From e69a8309435101e767d6a1a969d8d119792fc602 Mon Sep 17 00:00:00 2001
From: Ralf Klammer <ralf.klammer@tu-dresden.de>
Date: Wed, 12 Mar 2025 17:40:06 +0100
Subject: [PATCH 01/59] [metrics] First draft on mkdocs-version

---
 .gitignore                                    |   1 +
 README.bkp.md                                 | 173 ++++++++++++++++
 README.md                                     | 184 ++++--------------
 docs/index.md                                 |  21 ++
 docs/macros/__pycache__/main.cpython-312.pyc  | Bin 0 -> 1290 bytes
 docs/macros/main.py                           |  15 ++
 docs/metrics/04_outgoing_edges.md             |  37 ++++
 docs/metrics/05_incoming_edges.md             |  37 ++++
 docs/metrics/index.md                         |  10 +
 mkdocs.yml                                    |  18 ++
 .../examples}/Educational resources.rq        |   0
 {examples => queries/examples}/Metadata.rq    |   0
 .../examples}/Services in the NFDI4Earth.rq   |   0
 queries/metrics/GM0004_1.rq                   |   8 +
 queries/metrics/GM0004_2.rq                   |   6 +
 queries/metrics/GM0004_3.rq                   |   6 +
 queries/metrics/GM0004_4.rq                   |  14 ++
 queries/metrics/GM0005_1.rq                   |   7 +
 queries/metrics/GM0005_2.rq                   |   6 +
 queries/metrics/GM0005_3.rq                   |   6 +
 queries/metrics/GM0005_4.rq                   |  14 ++
 queries/metrics/GM001.rq                      |   6 +
 queries/metrics/GM002_1.rq                    |  13 ++
 queries/metrics/GM002_2.rq                    |  12 ++
 queries/metrics/GM002_3.rq                    |  12 ++
 queries/metrics/GM003_1.rq                    |  34 ++++
 queries/metrics/GM003_2.rq                    |  37 ++++
 queries/metrics/GM003_3.rq                    |  25 +++
 queries/metrics/GM003_4.rq                    |  26 +++
 queries/{ => questions}/AG001.rq              |   0
 queries/{ => questions}/AG001_2.rq            |   0
 queries/{ => questions}/AG002_1.rq            |   0
 queries/{ => questions}/AG002_2.rq            |   0
 queries/{ => questions}/AT001.rq              |   0
 queries/{ => questions}/AT002_1.rq            |   0
 queries/{ => questions}/AT002_2.rq            |   0
 queries/{ => questions}/DA001.rq              |   0
 queries/{ => questions}/DA002_1.rq            |   0
 queries/{ => questions}/DA002_2.rq            |   0
 queries/{ => questions}/DA003_1.rq            |   0
 queries/{ => questions}/LH001.rq              |   0
 queries/{ => questions}/LH002_1.rq            |   0
 queries/{ => questions}/LH002_2.rq            |   0
 queries/{ => questions}/LR001.rq              |   0
 queries/{ => questions}/LR002_1.rq            |   0
 queries/{ => questions}/LR002_2.rq            |   0
 queries/{ => questions}/MS001.rq              |   0
 queries/{ => questions}/MS002_1.rq            |   0
 queries/{ => questions}/MS002_2.rq            |   0
 queries/{ => questions}/OG001.rq              |   0
 queries/{ => questions}/OG002_1.rq            |   0
 queries/{ => questions}/OG002_2.rq            |   0
 queries/{ => questions}/PE001.rq              |   0
 queries/{ => questions}/PE002_1.rq            |   0
 queries/{ => questions}/PE002_2.rq            |   0
 queries/{ => questions}/REG001.rq             |   0
 queries/{ => questions}/REG002_1.rq           |   0
 queries/{ => questions}/REG002_2.rq           |   0
 queries/{ => questions}/REP001.rq             |   0
 queries/{ => questions}/REP002_1.rq           |   0
 queries/{ => questions}/REP002_2.rq           |   0
 queries/{ => questions}/RP001.rq              |   0
 queries/{ => questions}/RP002_1.rq            |   0
 queries/{ => questions}/RP002_2.rq            |   0
 queries/{ => questions}/SC001.rq              |   0
 queries/{ => questions}/SC002_1.rq            |   0
 queries/{ => questions}/SC002_2.rq            |   0
 queries/{ => questions}/TY001.rq              |   0
 queries/{ => questions}/old/DR001_1.rq        |   0
 queries/{ => questions}/old/OR001_1.rq        |   0
 queries/{ => questions}/old/OR001_2.rq        |   0
 queries/{ => questions}/old/OR002_1.rq        |   0
 queries/{ => questions}/old/OR003_1.rq        |   0
 queries/{ => questions}/old/OR004_1.rq        |   0
 queries/{ => questions}/old/OR005_1.rq        |   0
 queries/{ => questions}/old/OR006_1.rq        |   0
 reports/metrics/0004.txt                      |   4 +
 reports/metrics/0005.txt                      |   4 +
 requirements.txt                              |   3 +
 79 files changed, 592 insertions(+), 147 deletions(-)
 create mode 100644 .gitignore
 create mode 100644 README.bkp.md
 create mode 100644 docs/index.md
 create mode 100644 docs/macros/__pycache__/main.cpython-312.pyc
 create mode 100644 docs/macros/main.py
 create mode 100644 docs/metrics/04_outgoing_edges.md
 create mode 100644 docs/metrics/05_incoming_edges.md
 create mode 100644 docs/metrics/index.md
 create mode 100644 mkdocs.yml
 rename {examples => queries/examples}/Educational resources.rq (100%)
 rename {examples => queries/examples}/Metadata.rq (100%)
 rename {examples => queries/examples}/Services in the NFDI4Earth.rq (100%)
 create mode 100644 queries/metrics/GM0004_1.rq
 create mode 100644 queries/metrics/GM0004_2.rq
 create mode 100644 queries/metrics/GM0004_3.rq
 create mode 100644 queries/metrics/GM0004_4.rq
 create mode 100644 queries/metrics/GM0005_1.rq
 create mode 100644 queries/metrics/GM0005_2.rq
 create mode 100644 queries/metrics/GM0005_3.rq
 create mode 100644 queries/metrics/GM0005_4.rq
 create mode 100644 queries/metrics/GM001.rq
 create mode 100644 queries/metrics/GM002_1.rq
 create mode 100644 queries/metrics/GM002_2.rq
 create mode 100644 queries/metrics/GM002_3.rq
 create mode 100644 queries/metrics/GM003_1.rq
 create mode 100644 queries/metrics/GM003_2.rq
 create mode 100644 queries/metrics/GM003_3.rq
 create mode 100644 queries/metrics/GM003_4.rq
 rename queries/{ => questions}/AG001.rq (100%)
 rename queries/{ => questions}/AG001_2.rq (100%)
 rename queries/{ => questions}/AG002_1.rq (100%)
 rename queries/{ => questions}/AG002_2.rq (100%)
 rename queries/{ => questions}/AT001.rq (100%)
 rename queries/{ => questions}/AT002_1.rq (100%)
 rename queries/{ => questions}/AT002_2.rq (100%)
 rename queries/{ => questions}/DA001.rq (100%)
 rename queries/{ => questions}/DA002_1.rq (100%)
 rename queries/{ => questions}/DA002_2.rq (100%)
 rename queries/{ => questions}/DA003_1.rq (100%)
 rename queries/{ => questions}/LH001.rq (100%)
 rename queries/{ => questions}/LH002_1.rq (100%)
 rename queries/{ => questions}/LH002_2.rq (100%)
 rename queries/{ => questions}/LR001.rq (100%)
 rename queries/{ => questions}/LR002_1.rq (100%)
 rename queries/{ => questions}/LR002_2.rq (100%)
 rename queries/{ => questions}/MS001.rq (100%)
 rename queries/{ => questions}/MS002_1.rq (100%)
 rename queries/{ => questions}/MS002_2.rq (100%)
 rename queries/{ => questions}/OG001.rq (100%)
 rename queries/{ => questions}/OG002_1.rq (100%)
 rename queries/{ => questions}/OG002_2.rq (100%)
 rename queries/{ => questions}/PE001.rq (100%)
 rename queries/{ => questions}/PE002_1.rq (100%)
 rename queries/{ => questions}/PE002_2.rq (100%)
 rename queries/{ => questions}/REG001.rq (100%)
 rename queries/{ => questions}/REG002_1.rq (100%)
 rename queries/{ => questions}/REG002_2.rq (100%)
 rename queries/{ => questions}/REP001.rq (100%)
 rename queries/{ => questions}/REP002_1.rq (100%)
 rename queries/{ => questions}/REP002_2.rq (100%)
 rename queries/{ => questions}/RP001.rq (100%)
 rename queries/{ => questions}/RP002_1.rq (100%)
 rename queries/{ => questions}/RP002_2.rq (100%)
 rename queries/{ => questions}/SC001.rq (100%)
 rename queries/{ => questions}/SC002_1.rq (100%)
 rename queries/{ => questions}/SC002_2.rq (100%)
 rename queries/{ => questions}/TY001.rq (100%)
 rename queries/{ => questions}/old/DR001_1.rq (100%)
 rename queries/{ => questions}/old/OR001_1.rq (100%)
 rename queries/{ => questions}/old/OR001_2.rq (100%)
 rename queries/{ => questions}/old/OR002_1.rq (100%)
 rename queries/{ => questions}/old/OR003_1.rq (100%)
 rename queries/{ => questions}/old/OR004_1.rq (100%)
 rename queries/{ => questions}/old/OR005_1.rq (100%)
 rename queries/{ => questions}/old/OR006_1.rq (100%)
 create mode 100644 reports/metrics/0004.txt
 create mode 100644 reports/metrics/0005.txt
 create mode 100644 requirements.txt

diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..854d509
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1 @@
+*venv*
diff --git a/README.bkp.md b/README.bkp.md
new file mode 100644
index 0000000..6f8681c
--- /dev/null
+++ b/README.bkp.md
@@ -0,0 +1,173 @@
+# KnowledgeHub - Domain Coverage
+
+This is collection of relevant questions and corresponding SPARQL-Queries, that answer those questions. The questions are grouped according to the different entities of interest (datasets, organizations, ...). The entities appear in alphabetical order. The first query is useful to get an overview of all entities available in the Knowledge Hub. The questions listed below form, altogether, the domain coverage of the Knowledge Hub. For details, see the [NFDI4Earth Deliverable D4.3.2](https://zenodo.org/records/7950860).
+
+***Overview of the types of entities***
+| ID    | Question | Query/ies |
+|---|---|---|
+| TY001	| What are the types of entities available in the knowledge graph? | [TY001](queries/TY001.rq)|
+
+&nbsp;
+&nbsp;
+
+### Aggregator
+
+| ID    | Question | Query/ies |
+|---|---|---|
+| AG001	| What are all entities of type Aggregator? | [AG001](queries/AG001.rq)|
+| AG001_2	| What are name and geometry of Aggregator? | [AG001_2](queries/AG001_2.rq)|
+| AG002_1	| What are all attributes available for the type "Aggregator"? | [AG002_1](queries/AG002_1.rq)|
+| AG002_2	| How many attributes are available for the type "Aggregator"? | [AG002_2](queries/AG002_2.rq)|
+
+### Article
+
+| ID    | Question | Query/ies |
+|---|---|---|
+| AT001	| What are all entities of type schema:Article? | [AT001](queries/AT001.rq)|
+| AT002_1	| What are all attributes available for the type "schema:Article"? | [AT002_1](queries/AT002_1.rq)|
+| AT002_2	| How many attributes are available for the type "schema:Article"? | [AT002_2](queries/AT002_2.rq)|
+
+### Dataset
+
+| ID    | Question | Query/ies |
+|---|---|---|
+| DA001	| What are all entities of type dcat:Dataset? | [DA001](queries/DA001.rq)|
+| DA002_1	| What are all attributes available for the type "dcat:Dataset"? | [DA002_1](queries/DA002_1.rq)|
+| DA002_2	| How many attributes are available for the type "dcat:Dataset"? | [DA002_2](queries/DA002_2.rq)|
+| DA003_1	| What are the datasets having the string 'world settlement footprint' in title or description? | [DA003_1](queries/DA003_1.rq)|
+
+### LHBArticle
+
+| ID    | Question | Query/ies |
+|---|---|---|
+| LH001	| What are all entities of type LHBArticle? | [LH001](queries/LH001.rq)|
+| LH002_1	| What are all attributes available for the type "LHBArticle"? | [LH002_1](queries/LH002_1.rq)|
+| LH002_2	| How many attributes are available for the type "LHBArticle"? | [LH002_2](queries/LH002_2.rq)|
+
+### LearningResource
+
+| ID    | Question | Query/ies |
+|---|---|---|
+| LR001	| What are all entities of type LearningResource? | [LR001](queries/LR001.rq)|
+| LR002_1	| What are all attributes available for the type "LearningResource"? | [LR002_1](queries/LR002_1.rq)|
+| LR002_2	| How many attributes are available for the type "LearningResource"? | [LR002_2](queries/LR002_2.rq)|
+
+### MetadataStandard
+
+| ID    | Question | Query/ies |
+|---|---|---|
+| MS001	| What are all entities of type MetadataStandard? | [MS001](queries/MS001.rq)|
+| MS002_1	| What are all attributes available for the type "MetadataStandard"? | [MS002_1](queries/MS002_1.rq)|
+| MS002_2	| How many attributes are available for the type "MetadataStandard"? | [MS002_2](queries/MS002_2.rq)|
+
+### Organization
+
+| ID    | Question | Query/ies |
+|---|---|---|
+| OG001	| What are all entities of type Organization? | [OG001](queries/OG001.rq)|
+| OG002_1	| What are all attributes available for the type "Organization"? | [OG002_1](queries/OG002_1.rq)|
+| OG002_2	| How many attributes are available for the type "Organization"? | [OG002_2](queries/OG002_2.rq)|
+
+### Person
+
+| ID    | Question | Query/ies |
+|---|---|---|
+| PE001	| What are all entities of type Person? | [PE001](queries/PE001.rq)|
+| PE002_1	| What are all attributes available for the type "Person"? | [PE002_1](queries/PE002_1.rq)|
+| PE002_2	| How many attributes are available for the type "Person"? | [PE002_2](queries/PE002_2.rq)|
+
+### Registry
+
+| ID    | Question | Query/ies |
+|---|---|---|
+| REG001	| What are all entities of type Registry? | [REG001](queries/REG001.rq)|
+| REG002_1	| What are all attributes available for the type "Registry"? | [REG002_1](queries/REG002_1.rq)|
+| REG002_2	| How many attributes are available for the type "Registry"? | [REG002_2](queries/REG002_2.rq)|
+
+### Repository
+
+| ID    | Question | Query/ies |
+|---|---|---|
+| REP001	| What are all entities of type Repository? | [REP001](queries/REP001.rq)|
+| REP002_1	| What are all attributes available for the type "Repository"? | [REP002_1](queries/REP002_1.rq)|
+| REP002_2	| How many attributes are available for the type "Repository"? | [REP002_2](queries/REP002_2.rq)|
+
+### ResearchProject
+
+| ID    | Question | Query/ies |
+|---|---|---|
+| RP001	| What are all entities of type ResearchProject? | [RP001](queries/RP001.rq)|
+| RP002_1	| What are all attributes available for the type "ResearchProject"? | [RP002_1](queries/RP002_1.rq)|
+| RP002_2	| How many attributes are available for the type "ResearchProject"? | [RP002_2](queries/RP002_2.rq)|
+
+
+### SoftwareSourceCode
+
+| ID    | Question | Query/ies |
+|---|---|---|
+| SC001	| What are all entities of type SoftwareSourceCode? | [SC001](queries/SC001.rq)|
+| SC002_1	| What are all attributes available for the type "SoftwareSourceCode"? | [SC002_1](queries/SC002_1.rq)|
+| SC002_2	| How many attributes are available for the type "SoftwareSourceCode"? | [SC002_2](queries/SC002_2.rq)|
+
+### Graph Metrics
+
+| ID    | Question | Query/ies |
+|---|---|---|
+|GM001|The number of instances in a graph|[GM001](queries/GM001.rq)|
+|GM002|The number of assertions (or edges between entities)|[GM002](queries/GM002.rq)|
+||||
+||||
+||||
+||||
+
+
+<!--- Template for a new table (including first line)
+
+### EntityType
+
+| ID    | Question | Query/ies |
+|---|---|---|
+| XX001	| What are all entities of type EntityType? | [XX001](queries/XX001.rq)|
+
+-->
+
+
+<!---
+
+
+### Organizations
+
+| ID    | Question | Query/ies |
+|---|---|---|
+| OR001	| What is the URL of the homepage for the organization with the following name: 'Karlsruhe Institute of Technology'? | [OR001_1](queries/OR001_1.rq),[OR001_2](queries/OR001_2.rq) |
+| OR002 | What is the URL of the homepage for the organization with the following ID: 'https://nfdi4earth-knowledgehub.geo.tu-dresden.de/api/objects/n4ekh/a38143be5e15bed94a20' | [OR003_1](queries/OR003_1.rq) |
+| OR003 | Which organizations have not defined any homepage? | [OR003_1](queries/OR003_1.rq) |
+| OR004 | Which services are published by the organization? | [OR004_1](queries/OR004_1.rq) |
+| OR005 | What is the geolocation of the organization called 'TU Dresden'? | [OR005_1](queries/OR005_1.rq) |
+| OR006 | What is the geolocation of all organizations, that are members of the NFDI4Earth consortium? | [OR006_1](queries/OR006_1.rq) |
+
+### Repositories
+
+| ID | Question | Query/ies |
+|----|----------|-----------|
+| DR1 | At which repository can I archive my [geophysical] data of [2] GB?| [OR004_1](queries/OR004_1.rq) |
+| DR2 | What is the temporal coverage of a data repository?||
+| DR3 | What is the spatial coverage of a data repository?||
+| DR4 | What is the curation policy of the data repository?||
+| DR5 | Which licences are supported by the data repository?||
+| DR6 | Does the repository give identifiers for its ressources?||
+| DR7 | Which metadata harversting interface is supported by the repository?||
+| DR8 | Which type of (persistent) identifiers are used by the repository?||
+| DR9 | What is the thematic area/subject of a repository?||
+| DR10 | Limitations of data deposit at the repository?||
+| DR11 | When was the medatada for a given repository first collected/last updated?||
+| DR12 | Is the repository still available?||
+| DR13 | Which repository allows long term archiving?||
+
+-->
+
+# Notes
+
+This question-based approach takes inspiration from the [GeoSPARQLBenchmark](https://github.com/OpenLinkSoftware/GeoSPARQLBenchmark).
+
+It is directly linked to the [Knowledge Hub landing page project](https://git.rwth-aachen.de/nfdi4earth/knowledgehub/kh_landingpage) as all the questions and examples are taken to explain the basic idea and demonstrate usage of the [Knowledge Hub](https://knowledgehub.nfdi4earth.de).
diff --git a/README.md b/README.md
index afdcdbd..d750a1c 100644
--- a/README.md
+++ b/README.md
@@ -1,162 +1,52 @@
-# KnowledgeHub - Domain Coverage
+# NFDI4Earth - KnowledgeHub - KnowledgeGraph Analysis
 
-This is collection of relevant questions and corresponding SPARQL-Queries, that answer those questions. The questions are grouped according to the different entities of interest (datasets, organizations, ...). The entities appear in alphabetical order. The first query is useful to get an overview of all entities available in the Knowledge Hub. The questions listed below form, altogether, the domain coverage of the Knowledge Hub. For details, see the [NFDI4Earth Deliverable D4.3.2](https://zenodo.org/records/7950860).
+A collection of SPARQL queries for analyzing the KnowledgeGraph of NFDI4Earth's KnowledgeHub.
 
-***Overview of the types of entities***
-| ID    | Question | Query/ies |
-|---|---|---|
-| TY001	| What are the types of entities available in the knowledge graph? | [TY001](queries/TY001.rq)|
+## Repository Structure
 
-&nbsp;
-&nbsp;
+```
+queries/
+├── examples/       # Basic SPARQL examples
+├── questions/      # Domain-specific queries
+└── metrics/        # Graph analysis queries
 
-### Aggregator
+docs/
+├── examples.md     # Documentation for basic examples
+├── questions.md    # Documentation for domain questions
+└── metrics.md      # Documentation for graph metrics
+```
 
-| ID    | Question | Query/ies |
-|---|---|---|
-| AG001	| What are all entities of type Aggregator? | [AG001](queries/AG001.rq)|
-| AG001_2	| What are name and geometry of Aggregator? | [AG001_2](queries/AG001_2.rq)|
-| AG002_1	| What are all attributes available for the type "Aggregator"? | [AG002_1](queries/AG002_1.rq)|
-| AG002_2	| How many attributes are available for the type "Aggregator"? | [AG002_2](queries/AG002_2.rq)|
+## Usage
 
-### Article
+All queries are stored in `.rq` files and can be executed against the NFDI4Earth KnowledgeGraph endpoint.
 
-| ID    | Question | Query/ies |
-|---|---|---|
-| AT001	| What are all entities of type schema:Article? | [AT001](queries/AT001.rq)|
-| AT002_1	| What are all attributes available for the type "schema:Article"? | [AT002_1](queries/AT002_1.rq)|
-| AT002_2	| How many attributes are available for the type "schema:Article"? | [AT002_2](queries/AT002_2.rq)|
+### Local Development
 
-### Dataset
+0. Setup virtual environment
+```bash
+python3 -m venv venv
+. venv/bin/activate
+```
 
-| ID    | Question | Query/ies |
-|---|---|---|
-| DA001	| What are all entities of type dcat:Dataset? | [DA001](queries/DA001.rq)|
-| DA002_1	| What are all attributes available for the type "dcat:Dataset"? | [DA002_1](queries/DA002_1.rq)|
-| DA002_2	| How many attributes are available for the type "dcat:Dataset"? | [DA002_2](queries/DA002_2.rq)|
-| DA003_1	| What are the datasets having the string 'world settlement footprint' in title or description? | [DA003_1](queries/DA003_1.rq)|
+1. Install dependencies:
+```bash
+pip install -r requirements.txt
+```
 
-### LHBArticle
+2. Start local documentation server:
+```bash
+mkdocs serve
+```
 
-| ID    | Question | Query/ies |
-|---|---|---|
-| LH001	| What are all entities of type LHBArticle? | [LH001](queries/LH001.rq)|
-| LH002_1	| What are all attributes available for the type "LHBArticle"? | [LH002_1](queries/LH002_1.rq)|
-| LH002_2	| How many attributes are available for the type "LHBArticle"? | [LH002_2](queries/LH002_2.rq)|
+3. Build documentation:
+```bash
+mkdocs build
+```
 
-### LearningResource
+## Contributing
 
-| ID    | Question | Query/ies |
-|---|---|---|
-| LR001	| What are all entities of type LearningResource? | [LR001](queries/LR001.rq)|
-| LR002_1	| What are all attributes available for the type "LearningResource"? | [LR002_1](queries/LR002_1.rq)|
-| LR002_2	| How many attributes are available for the type "LearningResource"? | [LR002_2](queries/LR002_2.rq)|
+We welcome contributions! Please check our contribution guidelines for adding new queries.
 
-### MetadataStandard
+## Contact
 
-| ID    | Question | Query/ies |
-|---|---|---|
-| MS001	| What are all entities of type MetadataStandard? | [MS001](queries/MS001.rq)|
-| MS002_1	| What are all attributes available for the type "MetadataStandard"? | [MS002_1](queries/MS002_1.rq)|
-| MS002_2	| How many attributes are available for the type "MetadataStandard"? | [MS002_2](queries/MS002_2.rq)|
-
-### Organization
-
-| ID    | Question | Query/ies |
-|---|---|---|
-| OG001	| What are all entities of type Organization? | [OG001](queries/OG001.rq)|
-| OG002_1	| What are all attributes available for the type "Organization"? | [OG002_1](queries/OG002_1.rq)|
-| OG002_2	| How many attributes are available for the type "Organization"? | [OG002_2](queries/OG002_2.rq)|
-
-### Person
-
-| ID    | Question | Query/ies |
-|---|---|---|
-| PE001	| What are all entities of type Person? | [PE001](queries/PE001.rq)|
-| PE002_1	| What are all attributes available for the type "Person"? | [PE002_1](queries/PE002_1.rq)|
-| PE002_2	| How many attributes are available for the type "Person"? | [PE002_2](queries/PE002_2.rq)|
-
-### Registry
-
-| ID    | Question | Query/ies |
-|---|---|---|
-| REG001	| What are all entities of type Registry? | [REG001](queries/REG001.rq)|
-| REG002_1	| What are all attributes available for the type "Registry"? | [REG002_1](queries/REG002_1.rq)|
-| REG002_2	| How many attributes are available for the type "Registry"? | [REG002_2](queries/REG002_2.rq)|
-
-### Repository
-
-| ID    | Question | Query/ies |
-|---|---|---|
-| REP001	| What are all entities of type Repository? | [REP001](queries/REP001.rq)|
-| REP002_1	| What are all attributes available for the type "Repository"? | [REP002_1](queries/REP002_1.rq)|
-| REP002_2	| How many attributes are available for the type "Repository"? | [REP002_2](queries/REP002_2.rq)|
-
-### ResearchProject
-
-| ID    | Question | Query/ies |
-|---|---|---|
-| RP001	| What are all entities of type ResearchProject? | [RP001](queries/RP001.rq)|
-| RP002_1	| What are all attributes available for the type "ResearchProject"? | [RP002_1](queries/RP002_1.rq)|
-| RP002_2	| How many attributes are available for the type "ResearchProject"? | [RP002_2](queries/RP002_2.rq)|
-
-
-### SoftwareSourceCode
-
-| ID    | Question | Query/ies |
-|---|---|---|
-| SC001	| What are all entities of type SoftwareSourceCode? | [SC001](queries/SC001.rq)|
-| SC002_1	| What are all attributes available for the type "SoftwareSourceCode"? | [SC002_1](queries/SC002_1.rq)|
-| SC002_2	| How many attributes are available for the type "SoftwareSourceCode"? | [SC002_2](queries/SC002_2.rq)|
-
-
-<!--- Template for a new table (including first line)
-
-### EntityType
-
-| ID    | Question | Query/ies |
-|---|---|---|
-| XX001	| What are all entities of type EntityType? | [XX001](queries/XX001.rq)|
-
--->
-
-
-<!---
-
-
-### Organizations
-
-| ID    | Question | Query/ies |
-|---|---|---|
-| OR001	| What is the URL of the homepage for the organization with the following name: 'Karlsruhe Institute of Technology'? | [OR001_1](queries/OR001_1.rq),[OR001_2](queries/OR001_2.rq) |
-| OR002 | What is the URL of the homepage for the organization with the following ID: 'https://nfdi4earth-knowledgehub.geo.tu-dresden.de/api/objects/n4ekh/a38143be5e15bed94a20' | [OR003_1](queries/OR003_1.rq) |
-| OR003 | Which organizations have not defined any homepage? | [OR003_1](queries/OR003_1.rq) |
-| OR004 | Which services are published by the organization? | [OR004_1](queries/OR004_1.rq) |
-| OR005 | What is the geolocation of the organization called 'TU Dresden'? | [OR005_1](queries/OR005_1.rq) |
-| OR006 | What is the geolocation of all organizations, that are members of the NFDI4Earth consortium? | [OR006_1](queries/OR006_1.rq) |
-
-### Repositories
-
-| ID | Question | Query/ies |
-|----|----------|-----------|
-| DR1 | At which repository can I archive my [geophysical] data of [2] GB?| [OR004_1](queries/OR004_1.rq) |
-| DR2 | What is the temporal coverage of a data repository?||
-| DR3 | What is the spatial coverage of a data repository?||
-| DR4 | What is the curation policy of the data repository?||
-| DR5 | Which licences are supported by the data repository?||
-| DR6 | Does the repository give identifiers for its ressources?||
-| DR7 | Which metadata harversting interface is supported by the repository?||
-| DR8 | Which type of (persistent) identifiers are used by the repository?||
-| DR9 | What is the thematic area/subject of a repository?||
-| DR10 | Limitations of data deposit at the repository?||
-| DR11 | When was the medatada for a given repository first collected/last updated?||
-| DR12 | Is the repository still available?||
-| DR13 | Which repository allows long term archiving?||
-
--->
-
-# Notes
-
-This question-based approach takes inspiration from the [GeoSPARQLBenchmark](https://github.com/OpenLinkSoftware/GeoSPARQLBenchmark).
-
-It is directly linked to the [Knowledge Hub landing page project](https://git.rwth-aachen.de/nfdi4earth/knowledgehub/kh_landingpage) as all the questions and examples are taken to explain the basic idea and demonstrate usage of the [Knowledge Hub](https://knowledgehub.nfdi4earth.de).
+For questions about the NFDI4Earth KnowledgeHub Graph, contact [helpdesk@nfdi4earth.de](mailto:helpdesk@nfdi4earth.de?subject=[NFDI4Earth][KnowlegeGraph]).
diff --git a/docs/index.md b/docs/index.md
new file mode 100644
index 0000000..5d30e05
--- /dev/null
+++ b/docs/index.md
@@ -0,0 +1,21 @@
+# {{ config.site_name }}
+
+Welcome to the NFDI4Earth KnowledgeGraph Query Collection. This documentation provides insights into the NFDI4Earth KnowledgeHub Graph through SPARQL queries and their analysis.
+
+## Purpose
+
+The NFDI4Earth KnowledgeGraph represents a comprehensive network of earth science research data, connecting various domains, datasets, and research artifacts. Through SPARQL queries, we explore:
+
+- **Data Discovery**: Finding relevant research data across earth science domains
+- **Domain Coverage**: Understanding the breadth and depth of represented research areas
+- **Graph Structure**: Analyzing the knowledge graph's characteristics and connectivity
+
+## Exploration Areas
+
+We collect queries for three main purposes:
+
+1. **Basic Examples** to demonstrate common query patterns and graph exploration
+2. **Domain Questions** addressing specific research data discovery needs
+3. **Graph Metrics** providing insights into the knowledge graph's structure
+
+Each query is documented with its purpose, expected results, and practical implications for understanding and utilizing the NFDI4Earth KnowledgeHub.
diff --git a/docs/macros/__pycache__/main.cpython-312.pyc b/docs/macros/__pycache__/main.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..ae0eaebbb09fdee392a5b5d374f2647080079500
GIT binary patch
literal 1290
zcmX@j%ge>Uz`#(t?PB@{CI*JbAPx*OLK&YyN~SZUFtjj4F;p^YG9|-gKnj={7#KkK
zvlasb!*qrkhAfaM8ctzKVQgVs4Hd0rtYpw+_Dcq7g9<UQF)%O)GcYiGwqOJq!^uzr
zx1@$4g>f|#M4>4|EprWX7P3x=1Owb~mKw%J*%YQMR;UCLRl`!ln8F~%uo`N1GE)iz
z2*ONfWC&)cVQS>7VX0w~WME*xCXdTxd}b;$7%~(ymNPLjL^3ckFfvp!R4_*}lrt(b
zlz{vVcA*MG5ho)<3Udv^Y=#t;xy;E-kqp5MB^+Q$1_lNdh9VwDh7{Hs#@P%hY+zOF
zb6Fv(%Aw|Ia`@e1EV{)|s>#5>pyi#KnU|{IT9lrel$TjtoT^ZkT9kHpPkK^fk)DD}
zW~zcqVo7SILPC{9f<k#|QA(;pUS@Jei9&j6T4`QNYM!1}m7JDaYDP|Kkrh~dVqSV`
zdTLT?UaCS=YGzKVLY1gOYDH#oNoHzM2~=}to<c$ubAq0hChIMh{DRcHTbxCyi77dm
zd8x&>ShDgn^KJ>cW#**%<(IhSm*%Co78T_e-Qx1hOG&K&vo%?7ainGDq~;~&rrzQz
zE=epZi3jPv#a*14m!6Xf;V`D%Vg-p77lWc%0R(>e>1X8Urs@|Z=A`Mn<Y$-WrskET
z>ifC5c$&B-7L{bgd*|hs=cJ~mq^Ek6Cg}&3rWTiE=I1457NzQ^<R=&F=O!i><%6iq
zJiUU-TY{N+$vLGdsqvX<@nCNjSE;6?re)@(#;4|$>DlDuCnx3<+v#B_1EtmCLktWI
z4GbSxxH*#<?+A)c$XLL9Sy1Hz0~@c}3dS1}lJmJ|a?j?Sz<NVi_JXkH4Qbi=<}=L~
zn68joA$>vF^s=<scXl>jwT~e6A2=Aq6fcPC+>lmSAbn9<_d7RShKE5yYI^y^@)@O*
z>%Q}{@v40XY4`*pet+R(5K#QYz{r`zctg}~N5zh;3myp<gcC3EB;Mhd1W6<_-Vin3
z;I+Z#f_=z^(AWzhaTnqeF9;`H;z|0z%*dI{_^a59fq|ik$I*nLPt8$-c?+kb3Bx`%
zM-AqKEFk7VJ^>J04$RhIbu?r@sLSkV!f?<KOq%dJNirW|6$FtIj82lwnoNFvnvAzt
z!6B{5bc;DPudGOrfq|hI6lMwvMFI>A47a!tp<E=wz`y{?t;JH{@Vvpw(H_|t`H6{#
zHJR}<GXop{M+g&?P5d+&!Rl8s6p1o0Fn|sF#bJ}1pHiBWYF8u<Gm4vmf#Cx)BO~Ky
LCO*bwMzCT41cp1D

literal 0
HcmV?d00001

diff --git a/docs/macros/main.py b/docs/macros/main.py
new file mode 100644
index 0000000..4287ee7
--- /dev/null
+++ b/docs/macros/main.py
@@ -0,0 +1,15 @@
+def define_env(env):
+    @env.macro
+    def include_if_exists(filename, start_line=None, single_line=None):
+        try:
+            with open(filename, "r") as f:
+                lines = f.readlines()
+                if start_line is not None:
+                    return "".join(lines[start_line:])
+                elif single_line is not None:
+                    return lines[single_line]
+                return "".join(lines)
+        except FileNotFoundError:
+            return f"*Keine Ergebnisse verfügbar. Die Datei `{filename}` wurde nicht gefunden.*"
+        except IndexError:
+            return f"*Fehler: Die angegebene Zeile {start_line} existiert nicht in `{filename}`.*"
diff --git a/docs/metrics/04_outgoing_edges.md b/docs/metrics/04_outgoing_edges.md
new file mode 100644
index 0000000..8634ee7
--- /dev/null
+++ b/docs/metrics/04_outgoing_edges.md
@@ -0,0 +1,37 @@
+# Outgoing Edges
+
+This metric determines the median number of outgoing edges across all nodes in the graph. The calculation requires multiple steps.
+
+## Queries
+
+### Step 1: Count Outgoing Edges Per Node
+
+```sparql
+{{ include_if_exists("queries/metrics/GM0004_1.rq") }}
+```
+
+### Step 2: Get Total Number of Nodes
+
+```sparql
+{{ include_if_exists("queries/metrics/GM0004_2.rq") }}
+```
+
+### Step 3: Calculate Median Position
+
+Using the total node count (n), median position is: position = (n+1)/2
+
+```sparql
+{{ include_if_exists("queries/metrics/GM0004_3.rq") }}
+```
+
+### Step 4: Get Median Value(s)
+
+```sparql
+{{ include_if_exists("queries/metrics/GM0004_4.rq") }}
+```
+
+## Results
+{{ include_if_exists("reports/metrics/0004.txt", start_line=1) }}
+
+Last execution:
+{{ include_if_exists("reports/metrics/0004.txt", single_line=0) }}
\ No newline at end of file
diff --git a/docs/metrics/05_incoming_edges.md b/docs/metrics/05_incoming_edges.md
new file mode 100644
index 0000000..f7670f9
--- /dev/null
+++ b/docs/metrics/05_incoming_edges.md
@@ -0,0 +1,37 @@
+# Incoming Edges
+
+This metric determines the median number of incoming edges across all nodes in the graph. The calculation requires multiple steps.
+
+## Queries
+
+### Step 1: Count Incoming Edges Per Node
+
+```sparql
+{{ include_if_exists("queries/metrics/GM0005_1.rq") }}
+```
+
+### Step 2: Get Total Number of Nodes
+
+```sparql
+{{ include_if_exists("queries/metrics/GM0005_2.rq") }}
+```
+
+### Step 3: Calculate Median Position
+
+Using the total node count (n), median position is: position = (n+1)/2
+
+```sparql
+{{ include_if_exists("queries/metrics/GM0005_3.rq") }}
+```
+
+### Step 4: Get Median Value(s)
+
+```sparql
+{{ include_if_exists("queries/metrics/GM0005_4.rq") }}
+```
+
+## Results
+{{ include_if_exists("reports/metrics/0005.txt", start_line=1) }}
+
+Last execution:
+{{ include_if_exists("reports/metrics/0005.txt", single_line=0) }}
diff --git a/docs/metrics/index.md b/docs/metrics/index.md
new file mode 100644
index 0000000..380ab61
--- /dev/null
+++ b/docs/metrics/index.md
@@ -0,0 +1,10 @@
+# Metrics
+
+The KnowledgeGraph metrics provide quantitative insights into the structure and content of our knowledge graph. These measurements help us to:
+
+- Understand the graph's size and complexity
+- Evaluate the coverage of earth science domains
+- Identify areas for potential improvement
+- Monitor the graph's growth and development
+
+The queries are stored in separate `.rq` files and can be executed against the NFDI4Earth [KnowledgeGraph endpoint](https://sparql.knowledgehub.nfdi4earth.de).
diff --git a/mkdocs.yml b/mkdocs.yml
new file mode 100644
index 0000000..f20a844
--- /dev/null
+++ b/mkdocs.yml
@@ -0,0 +1,18 @@
+site_name: NFDI4Earth - KnowledgeGraph - Questions & Metrics
+
+nav:
+  - Home: index.md
+  - Questions: questions.md
+  - Metrics: metrics/
+
+plugins:
+  - search
+  - awesome-nav
+  - macros:
+      module_name: docs/macros/main
+
+# markdown_extensions:
+  # - pymdownx.superfences
+  # - pymdownx.snippets:
+  #     base_path: ['.']
+  #     check_paths: true
\ No newline at end of file
diff --git a/examples/Educational resources.rq b/queries/examples/Educational resources.rq
similarity index 100%
rename from examples/Educational resources.rq
rename to queries/examples/Educational resources.rq
diff --git a/examples/Metadata.rq b/queries/examples/Metadata.rq
similarity index 100%
rename from examples/Metadata.rq
rename to queries/examples/Metadata.rq
diff --git a/examples/Services in the NFDI4Earth.rq b/queries/examples/Services in the NFDI4Earth.rq
similarity index 100%
rename from examples/Services in the NFDI4Earth.rq
rename to queries/examples/Services in the NFDI4Earth.rq
diff --git a/queries/metrics/GM0004_1.rq b/queries/metrics/GM0004_1.rq
new file mode 100644
index 0000000..8a457d5
--- /dev/null
+++ b/queries/metrics/GM0004_1.rq
@@ -0,0 +1,8 @@
+# Count Outgoing Edges Per Node
+
+SELECT ?source (COUNT(?outgoing) as ?outEdges)
+WHERE {
+  ?source ?p ?outgoing .
+}
+GROUP BY ?source
+ORDER BY DESC(?outEdges)
diff --git a/queries/metrics/GM0004_2.rq b/queries/metrics/GM0004_2.rq
new file mode 100644
index 0000000..d7ff687
--- /dev/null
+++ b/queries/metrics/GM0004_2.rq
@@ -0,0 +1,6 @@
+# Returns a single number representing the cleaned count of unique outging nodes in the graph.
+
+SELECT (COUNT(DISTINCT ?source) as ?uniqueEdges)
+WHERE {
+  ?source ?p ?outgoing .
+}
\ No newline at end of file
diff --git a/queries/metrics/GM0004_3.rq b/queries/metrics/GM0004_3.rq
new file mode 100644
index 0000000..165999b
--- /dev/null
+++ b/queries/metrics/GM0004_3.rq
@@ -0,0 +1,6 @@
+# Returns a single number representing the median position of outgoing edges per node
+
+SELECT (COUNT(DISTINCT ?source) as ?uniqueNodes) / 2
+WHERE {
+  ?source ?p ?outgoing .
+}
\ No newline at end of file
diff --git a/queries/metrics/GM0004_4.rq b/queries/metrics/GM0004_4.rq
new file mode 100644
index 0000000..4249889
--- /dev/null
+++ b/queries/metrics/GM0004_4.rq
@@ -0,0 +1,14 @@
+# This SPARQL query calculates the median out-edge in a graph.
+# The placeholder {median_position} must be replaced with the actual position of the median.
+
+SELECT ?outEdges as ?outMedian
+WHERE {
+  SELECT ?source (COUNT(?outgoing) as ?outEdges)
+  WHERE {
+    ?source ?p ?outgoing .
+  }
+  GROUP BY ?source
+  ORDER BY ASC(?outEdges)
+}
+OFFSET {median_position}
+LIMIT 1
diff --git a/queries/metrics/GM0005_1.rq b/queries/metrics/GM0005_1.rq
new file mode 100644
index 0000000..86d531f
--- /dev/null
+++ b/queries/metrics/GM0005_1.rq
@@ -0,0 +1,7 @@
+# Count Incoming Edges Per Node
+
+SELECT ?target (COUNT(?incoming) as ?inEdges)
+WHERE {
+  ?incoming ?p ?target .
+}
+GROUP BY ?target
diff --git a/queries/metrics/GM0005_2.rq b/queries/metrics/GM0005_2.rq
new file mode 100644
index 0000000..3c319e1
--- /dev/null
+++ b/queries/metrics/GM0005_2.rq
@@ -0,0 +1,6 @@
+# Returns a single number representing the cleaned count of unique incoming nodes in the graph.
+
+SELECT (COUNT(DISTINCT ?target) as ?uniqueEdges)
+WHERE {
+  ?incoming ?p ?target .
+}
\ No newline at end of file
diff --git a/queries/metrics/GM0005_3.rq b/queries/metrics/GM0005_3.rq
new file mode 100644
index 0000000..3c386ee
--- /dev/null
+++ b/queries/metrics/GM0005_3.rq
@@ -0,0 +1,6 @@
+# Returns a single number representing the median position of incoming edges
+
+SELECT (COUNT(DISTINCT ?target) as ?uniqueEdges) / 2
+WHERE {
+  ?incoming ?p ?target .
+}
\ No newline at end of file
diff --git a/queries/metrics/GM0005_4.rq b/queries/metrics/GM0005_4.rq
new file mode 100644
index 0000000..952626e
--- /dev/null
+++ b/queries/metrics/GM0005_4.rq
@@ -0,0 +1,14 @@
+# This SPARQL query calculates the median in-edge a graph.
+# The placeholder {median_position} must be replaced with the actual position of the median.
+
+SELECT ?inEdges as ?inMedian
+WHERE {
+    SELECT ?target (COUNT(?incoming) as ?inEdges)
+    WHERE {
+    ?incoming ?p ?target .
+    }
+    GROUP BY ?target
+    ORDER BY ASC(?inEdges)
+}
+OFFSET {median_position}
+LIMIT 1
diff --git a/queries/metrics/GM001.rq b/queries/metrics/GM001.rq
new file mode 100644
index 0000000..95b0c14
--- /dev/null
+++ b/queries/metrics/GM001.rq
@@ -0,0 +1,6 @@
+# The number of instances in a graph
+
+SELECT (COUNT(?instance) AS ?numInstances)
+WHERE {
+  ?instance a [] .
+}
diff --git a/queries/metrics/GM002_1.rq b/queries/metrics/GM002_1.rq
new file mode 100644
index 0000000..0bdd96a
--- /dev/null
+++ b/queries/metrics/GM002_1.rq
@@ -0,0 +1,13 @@
+# Total number of assertions (between entities & literals)
+#
+# Explanation:
+# ?subject ?predicate ?object searches through all triples (assertions) in the dataset.
+# COUNT(*) AS ?numAssertions counts the number of triples.
+#
+# This query returns the total number of edges in the graph, regardless of
+# whether they exist between entities or between entities and literals.
+
+SELECT (COUNT(*) AS ?numAssertions)
+WHERE {
+  ?subject ?predicate ?object .
+}
diff --git a/queries/metrics/GM002_2.rq b/queries/metrics/GM002_2.rq
new file mode 100644
index 0000000..a20aee9
--- /dev/null
+++ b/queries/metrics/GM002_2.rq
@@ -0,0 +1,12 @@
+# The number of assertions between entities (without literals)
+#
+# Explanation:
+#   ?subject ?predicate ?object searches through all triples.
+#   FILTER(isIRI(?object)) checks if the object is an IRI (i.e. an entity) and not a literal (e.g. not a string, date, or number).
+#   COUNT(*) AS ?numEntityEdges counts the number of corresponding edges.
+
+SELECT (COUNT(*) AS ?numEntityEdges)
+WHERE {
+  ?subject ?predicate ?object .
+  FILTER(isIRI(?object))  # Only objects that are URIs (i.e. entities)
+}
diff --git a/queries/metrics/GM002_3.rq b/queries/metrics/GM002_3.rq
new file mode 100644
index 0000000..ec6ab14
--- /dev/null
+++ b/queries/metrics/GM002_3.rq
@@ -0,0 +1,12 @@
+# The number of assertions between literals (without entities)
+#
+# Explanation:
+#   ?subject ?predicate ?object searches through all triples.
+#   FILTER(isLiteral(?object)) ensures that only edges are counted where the object is a literal (not an IRI).
+#   COUNT(*) AS ?numLiteralAssertions counts the number of corresponding edges.
+
+SELECT (COUNT(*) AS ?numLiteralAssertions)
+WHERE {
+  ?subject ?predicate ?object .
+  FILTER(isLiteral(?object))  # Only objects that are literals
+}
diff --git a/queries/metrics/GM003_1.rq b/queries/metrics/GM003_1.rq
new file mode 100644
index 0000000..db52f5b
--- /dev/null
+++ b/queries/metrics/GM003_1.rq
@@ -0,0 +1,34 @@
+# The average linkage degree
+# (i.e.: how many assertions per entity does the graph contain)
+#
+# WARNING: This query can be very resource-intensive and slow on large datasets!
+# It might even timeout or crash for graphs with millions of triples.
+# Consider using batch processing with LIMIT and OFFSET for large datasets.
+#
+# Explanation:
+#   First count outgoing edges per entity
+#   Then count incoming edges per entity
+#   Add both counts and calculate average
+
+SELECT (AVG(?totalDegree) as ?avgLinkageDegree)
+WHERE {
+  {
+    SELECT ?entity ((?outDegree + ?inDegree) as ?totalDegree)
+    WHERE {
+      {
+        SELECT ?entity (COUNT(*) as ?outDegree)
+        WHERE {
+          ?entity ?p ?o .
+        }
+        GROUP BY ?entity
+      }
+      {
+        SELECT ?entity (COUNT(*) as ?inDegree)
+        WHERE {
+          ?s ?p ?entity .
+        }
+        GROUP BY ?entity
+      }
+    }
+  }
+}
diff --git a/queries/metrics/GM003_2.rq b/queries/metrics/GM003_2.rq
new file mode 100644
index 0000000..de8c8e1
--- /dev/null
+++ b/queries/metrics/GM003_2.rq
@@ -0,0 +1,37 @@
+# The average linkage degree with batch processing
+# (i.e.: how many assertions per entity does the graph contain)
+#
+# Explanation:
+#   This is an alternative version (esp. for testing purposes)
+#   as it enables a limitation of analysed entities
+
+SELECT (AVG(?totalDegree) as ?avgLinkageDegree)
+WHERE {
+  {
+    SELECT ?entity ((?outDegree + ?inDegree) as ?totalDegree)
+    WHERE {
+      {
+        SELECT DISTINCT ?entity
+        WHERE {
+          { ?entity ?p ?o } UNION { ?s ?p ?entity }
+        }
+        LIMIT 1000  # Process 1000 entities at a time
+        OFFSET 0    # Start with first batch, increment for next batches
+      }
+      {
+        SELECT ?entity (COUNT(*) as ?outDegree)
+        WHERE {
+          ?entity ?p ?o .
+        }
+        GROUP BY ?entity
+      }
+      {
+        SELECT ?entity (COUNT(*) as ?inDegree)
+        WHERE {
+          ?s ?p ?entity .
+        }
+        GROUP BY ?entity
+      }
+    }
+  }
+}
\ No newline at end of file
diff --git a/queries/metrics/GM003_3.rq b/queries/metrics/GM003_3.rq
new file mode 100644
index 0000000..3f1028a
--- /dev/null
+++ b/queries/metrics/GM003_3.rq
@@ -0,0 +1,25 @@
+# The average outgoing linkage degree
+# (i.e.: how many outgoing assertions per entity does the graph contain)
+#
+# Explanation:
+#   This is a simplified version that only counts outgoing edges
+#   Limited to 1000 entities for testing purposes
+
+SELECT (AVG(?outDegree) as ?avgOutgoingLinkageDegree)
+WHERE {
+  {
+    SELECT ?entity (COUNT(*) as ?outDegree)
+    WHERE {
+      {
+        SELECT DISTINCT ?entity
+        WHERE {
+          ?entity ?p ?o .
+        }
+        LIMIT 1000  # Process 1000 entities at a time
+        OFFSET 0    # Start with first batch
+      }
+      ?entity ?p ?o .
+    }
+    GROUP BY ?entity
+  }
+}
\ No newline at end of file
diff --git a/queries/metrics/GM003_4.rq b/queries/metrics/GM003_4.rq
new file mode 100644
index 0000000..1a61203
--- /dev/null
+++ b/queries/metrics/GM003_4.rq
@@ -0,0 +1,26 @@
+# The average incoming linkage degree
+# (i.e.: how many incoming assertions per entity does the graph contain)
+#
+# Explanation:
+#   Simple version that only counts incoming edges per entity
+#   No complex sub-selects needed for this case
+#   Limited to 1000 entities for testing purposes
+
+SELECT (AVG(?inDegree) as ?avgIncomingLinkageDegree)
+WHERE {
+  {
+    SELECT ?entity (COUNT(*) as ?inDegree)
+    WHERE {
+      {
+        SELECT DISTINCT ?entity
+        WHERE {
+          ?s ?p ?entity .
+        }
+        LIMIT 1000  # Process 1000 entities at a time
+        OFFSET 0    # Start with first batch
+      }
+      ?s ?p ?entity .
+    }
+    GROUP BY ?entity
+  }
+}
\ No newline at end of file
diff --git a/queries/AG001.rq b/queries/questions/AG001.rq
similarity index 100%
rename from queries/AG001.rq
rename to queries/questions/AG001.rq
diff --git a/queries/AG001_2.rq b/queries/questions/AG001_2.rq
similarity index 100%
rename from queries/AG001_2.rq
rename to queries/questions/AG001_2.rq
diff --git a/queries/AG002_1.rq b/queries/questions/AG002_1.rq
similarity index 100%
rename from queries/AG002_1.rq
rename to queries/questions/AG002_1.rq
diff --git a/queries/AG002_2.rq b/queries/questions/AG002_2.rq
similarity index 100%
rename from queries/AG002_2.rq
rename to queries/questions/AG002_2.rq
diff --git a/queries/AT001.rq b/queries/questions/AT001.rq
similarity index 100%
rename from queries/AT001.rq
rename to queries/questions/AT001.rq
diff --git a/queries/AT002_1.rq b/queries/questions/AT002_1.rq
similarity index 100%
rename from queries/AT002_1.rq
rename to queries/questions/AT002_1.rq
diff --git a/queries/AT002_2.rq b/queries/questions/AT002_2.rq
similarity index 100%
rename from queries/AT002_2.rq
rename to queries/questions/AT002_2.rq
diff --git a/queries/DA001.rq b/queries/questions/DA001.rq
similarity index 100%
rename from queries/DA001.rq
rename to queries/questions/DA001.rq
diff --git a/queries/DA002_1.rq b/queries/questions/DA002_1.rq
similarity index 100%
rename from queries/DA002_1.rq
rename to queries/questions/DA002_1.rq
diff --git a/queries/DA002_2.rq b/queries/questions/DA002_2.rq
similarity index 100%
rename from queries/DA002_2.rq
rename to queries/questions/DA002_2.rq
diff --git a/queries/DA003_1.rq b/queries/questions/DA003_1.rq
similarity index 100%
rename from queries/DA003_1.rq
rename to queries/questions/DA003_1.rq
diff --git a/queries/LH001.rq b/queries/questions/LH001.rq
similarity index 100%
rename from queries/LH001.rq
rename to queries/questions/LH001.rq
diff --git a/queries/LH002_1.rq b/queries/questions/LH002_1.rq
similarity index 100%
rename from queries/LH002_1.rq
rename to queries/questions/LH002_1.rq
diff --git a/queries/LH002_2.rq b/queries/questions/LH002_2.rq
similarity index 100%
rename from queries/LH002_2.rq
rename to queries/questions/LH002_2.rq
diff --git a/queries/LR001.rq b/queries/questions/LR001.rq
similarity index 100%
rename from queries/LR001.rq
rename to queries/questions/LR001.rq
diff --git a/queries/LR002_1.rq b/queries/questions/LR002_1.rq
similarity index 100%
rename from queries/LR002_1.rq
rename to queries/questions/LR002_1.rq
diff --git a/queries/LR002_2.rq b/queries/questions/LR002_2.rq
similarity index 100%
rename from queries/LR002_2.rq
rename to queries/questions/LR002_2.rq
diff --git a/queries/MS001.rq b/queries/questions/MS001.rq
similarity index 100%
rename from queries/MS001.rq
rename to queries/questions/MS001.rq
diff --git a/queries/MS002_1.rq b/queries/questions/MS002_1.rq
similarity index 100%
rename from queries/MS002_1.rq
rename to queries/questions/MS002_1.rq
diff --git a/queries/MS002_2.rq b/queries/questions/MS002_2.rq
similarity index 100%
rename from queries/MS002_2.rq
rename to queries/questions/MS002_2.rq
diff --git a/queries/OG001.rq b/queries/questions/OG001.rq
similarity index 100%
rename from queries/OG001.rq
rename to queries/questions/OG001.rq
diff --git a/queries/OG002_1.rq b/queries/questions/OG002_1.rq
similarity index 100%
rename from queries/OG002_1.rq
rename to queries/questions/OG002_1.rq
diff --git a/queries/OG002_2.rq b/queries/questions/OG002_2.rq
similarity index 100%
rename from queries/OG002_2.rq
rename to queries/questions/OG002_2.rq
diff --git a/queries/PE001.rq b/queries/questions/PE001.rq
similarity index 100%
rename from queries/PE001.rq
rename to queries/questions/PE001.rq
diff --git a/queries/PE002_1.rq b/queries/questions/PE002_1.rq
similarity index 100%
rename from queries/PE002_1.rq
rename to queries/questions/PE002_1.rq
diff --git a/queries/PE002_2.rq b/queries/questions/PE002_2.rq
similarity index 100%
rename from queries/PE002_2.rq
rename to queries/questions/PE002_2.rq
diff --git a/queries/REG001.rq b/queries/questions/REG001.rq
similarity index 100%
rename from queries/REG001.rq
rename to queries/questions/REG001.rq
diff --git a/queries/REG002_1.rq b/queries/questions/REG002_1.rq
similarity index 100%
rename from queries/REG002_1.rq
rename to queries/questions/REG002_1.rq
diff --git a/queries/REG002_2.rq b/queries/questions/REG002_2.rq
similarity index 100%
rename from queries/REG002_2.rq
rename to queries/questions/REG002_2.rq
diff --git a/queries/REP001.rq b/queries/questions/REP001.rq
similarity index 100%
rename from queries/REP001.rq
rename to queries/questions/REP001.rq
diff --git a/queries/REP002_1.rq b/queries/questions/REP002_1.rq
similarity index 100%
rename from queries/REP002_1.rq
rename to queries/questions/REP002_1.rq
diff --git a/queries/REP002_2.rq b/queries/questions/REP002_2.rq
similarity index 100%
rename from queries/REP002_2.rq
rename to queries/questions/REP002_2.rq
diff --git a/queries/RP001.rq b/queries/questions/RP001.rq
similarity index 100%
rename from queries/RP001.rq
rename to queries/questions/RP001.rq
diff --git a/queries/RP002_1.rq b/queries/questions/RP002_1.rq
similarity index 100%
rename from queries/RP002_1.rq
rename to queries/questions/RP002_1.rq
diff --git a/queries/RP002_2.rq b/queries/questions/RP002_2.rq
similarity index 100%
rename from queries/RP002_2.rq
rename to queries/questions/RP002_2.rq
diff --git a/queries/SC001.rq b/queries/questions/SC001.rq
similarity index 100%
rename from queries/SC001.rq
rename to queries/questions/SC001.rq
diff --git a/queries/SC002_1.rq b/queries/questions/SC002_1.rq
similarity index 100%
rename from queries/SC002_1.rq
rename to queries/questions/SC002_1.rq
diff --git a/queries/SC002_2.rq b/queries/questions/SC002_2.rq
similarity index 100%
rename from queries/SC002_2.rq
rename to queries/questions/SC002_2.rq
diff --git a/queries/TY001.rq b/queries/questions/TY001.rq
similarity index 100%
rename from queries/TY001.rq
rename to queries/questions/TY001.rq
diff --git a/queries/old/DR001_1.rq b/queries/questions/old/DR001_1.rq
similarity index 100%
rename from queries/old/DR001_1.rq
rename to queries/questions/old/DR001_1.rq
diff --git a/queries/old/OR001_1.rq b/queries/questions/old/OR001_1.rq
similarity index 100%
rename from queries/old/OR001_1.rq
rename to queries/questions/old/OR001_1.rq
diff --git a/queries/old/OR001_2.rq b/queries/questions/old/OR001_2.rq
similarity index 100%
rename from queries/old/OR001_2.rq
rename to queries/questions/old/OR001_2.rq
diff --git a/queries/old/OR002_1.rq b/queries/questions/old/OR002_1.rq
similarity index 100%
rename from queries/old/OR002_1.rq
rename to queries/questions/old/OR002_1.rq
diff --git a/queries/old/OR003_1.rq b/queries/questions/old/OR003_1.rq
similarity index 100%
rename from queries/old/OR003_1.rq
rename to queries/questions/old/OR003_1.rq
diff --git a/queries/old/OR004_1.rq b/queries/questions/old/OR004_1.rq
similarity index 100%
rename from queries/old/OR004_1.rq
rename to queries/questions/old/OR004_1.rq
diff --git a/queries/old/OR005_1.rq b/queries/questions/old/OR005_1.rq
similarity index 100%
rename from queries/old/OR005_1.rq
rename to queries/questions/old/OR005_1.rq
diff --git a/queries/old/OR006_1.rq b/queries/questions/old/OR006_1.rq
similarity index 100%
rename from queries/old/OR006_1.rq
rename to queries/questions/old/OR006_1.rq
diff --git a/reports/metrics/0004.txt b/reports/metrics/0004.txt
new file mode 100644
index 0000000..6b57c75
--- /dev/null
+++ b/reports/metrics/0004.txt
@@ -0,0 +1,4 @@
+2024-03-12 15:20
+- Total Edges: 6705836
+- Median Position: 3352918
+- Median Outgoing Edges: 2
\ No newline at end of file
diff --git a/reports/metrics/0005.txt b/reports/metrics/0005.txt
new file mode 100644
index 0000000..d337df0
--- /dev/null
+++ b/reports/metrics/0005.txt
@@ -0,0 +1,4 @@
+2024-03-12 15:20
+- Total Edges: 15111836
+- Median Position: 7555918
+- Median Outgoing Edges: 1
\ No newline at end of file
diff --git a/requirements.txt b/requirements.txt
new file mode 100644
index 0000000..0017228
--- /dev/null
+++ b/requirements.txt
@@ -0,0 +1,3 @@
+mkdocs
+mkdocs-macros-plugin
+mkdocs-awesome-nav
-- 
GitLab


From 0b061530cb787c079c06cf8dd9660b2cbb54c35e Mon Sep 17 00:00:00 2001
From: Ralf Klammer <ralf.klammer@tu-dresden.de>
Date: Thu, 13 Mar 2025 09:29:09 +0100
Subject: [PATCH 02/59] [metrics] Switch to materials-theme

---
 mkdocs.yml       | 24 +++++++++++++++++++-----
 requirements.txt |  1 +
 2 files changed, 20 insertions(+), 5 deletions(-)

diff --git a/mkdocs.yml b/mkdocs.yml
index f20a844..7184e25 100644
--- a/mkdocs.yml
+++ b/mkdocs.yml
@@ -5,14 +5,28 @@ nav:
   - Questions: questions.md
   - Metrics: metrics/
 
+theme:
+  name: material
+  language: de
+  features:
+    - search.highlight
+    - content.code.copy
+
 plugins:
   - search
   - awesome-nav
   - macros:
       module_name: docs/macros/main
 
-# markdown_extensions:
-  # - pymdownx.superfences
-  # - pymdownx.snippets:
-  #     base_path: ['.']
-  #     check_paths: true
\ No newline at end of file
+markdown_extensions:
+  - admonition
+  - pymdownx.details
+#   - pymdownx.superfences
+#   - pymdownx.snippets:
+#       base_path: ['.']
+#       check_paths: true
+#   - pymdownx.highlight:
+#       anchor_linenums: true
+#       line_spans: __span
+#       pygments_lang_class: true
+#   - pymdownx.inlinehilite
\ No newline at end of file
diff --git a/requirements.txt b/requirements.txt
index 0017228..b7e9d53 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,3 +1,4 @@
 mkdocs
+mkdocs-material
 mkdocs-macros-plugin
 mkdocs-awesome-nav
-- 
GitLab


From 89a9af989d21854407c4e53b860916f91afe2342 Mon Sep 17 00:00:00 2001
From: Ralf Klammer <ralf.klammer@tu-dresden.de>
Date: Thu, 13 Mar 2025 09:29:43 +0100
Subject: [PATCH 03/59] [metrics] Introduce "general metrics" section

---
 docs/metrics/general metrics/01_instances.md  | 24 ++++++++
 docs/metrics/general metrics/02_assertions.md | 36 ++++++++++++
 .../general metrics/03_linkage_degree.md      | 57 +++++++++++++++++++
 .../04_outgoing_edges.md                      | 14 ++---
 .../05_incoming_edges.md                      | 14 ++---
 docs/metrics/{ => general metrics}/index.md   |  2 +-
 queries/metrics/{GM0004_1.rq => GM004_1.rq}   |  1 -
 queries/metrics/{GM0004_2.rq => GM004_2.rq}   |  0
 queries/metrics/{GM0004_3.rq => GM004_3.rq}   |  2 +-
 queries/metrics/{GM0004_4.rq => GM004_4.rq}   |  0
 queries/metrics/{GM0005_1.rq => GM005_1.rq}   |  1 +
 queries/metrics/{GM0005_2.rq => GM005_2.rq}   |  0
 queries/metrics/{GM0005_3.rq => GM005_3.rq}   |  0
 queries/metrics/{GM0005_4.rq => GM005_4.rq}   |  0
 reports/metrics/{0004.txt => 004.txt}         |  0
 reports/metrics/{0005.txt => 005.txt}         |  0
 16 files changed, 134 insertions(+), 17 deletions(-)
 create mode 100644 docs/metrics/general metrics/01_instances.md
 create mode 100644 docs/metrics/general metrics/02_assertions.md
 create mode 100644 docs/metrics/general metrics/03_linkage_degree.md
 rename docs/metrics/{ => general metrics}/04_outgoing_edges.md (55%)
 rename docs/metrics/{ => general metrics}/05_incoming_edges.md (55%)
 rename docs/metrics/{ => general metrics}/index.md (97%)
 rename queries/metrics/{GM0004_1.rq => GM004_1.rq} (84%)
 rename queries/metrics/{GM0004_2.rq => GM004_2.rq} (100%)
 rename queries/metrics/{GM0004_3.rq => GM004_3.rq} (87%)
 rename queries/metrics/{GM0004_4.rq => GM004_4.rq} (100%)
 rename queries/metrics/{GM0005_1.rq => GM005_1.rq} (85%)
 rename queries/metrics/{GM0005_2.rq => GM005_2.rq} (100%)
 rename queries/metrics/{GM0005_3.rq => GM005_3.rq} (100%)
 rename queries/metrics/{GM0005_4.rq => GM005_4.rq} (100%)
 rename reports/metrics/{0004.txt => 004.txt} (100%)
 rename reports/metrics/{0005.txt => 005.txt} (100%)

diff --git a/docs/metrics/general metrics/01_instances.md b/docs/metrics/general metrics/01_instances.md
new file mode 100644
index 0000000..001bbf2
--- /dev/null
+++ b/docs/metrics/general metrics/01_instances.md	
@@ -0,0 +1,24 @@
+# 01 - Instances in a graph
+
+This metric counts the total number of instances (nodes) in an RDF graph. An instance is counted as any node that appears as either a subject or object in a triple. The instance count provides a fundamental measure of the graph's size and gives a first indication of its complexity.
+
+The metric helps to:
+
+- Understand the overall scale of the knowledge graph
+- Track growth over time
+- Compare different graph versions or datasets
+- Establish a baseline for other metrics
+
+## Queries
+
+### Count Number of instances in a graph
+
+```sparql
+{{ include_if_exists("queries/metrics/GM001.rq") }}
+```
+
+## Results
+{{ include_if_exists("reports/metrics/001.txt", start_line=1) }}
+
+Last execution:
+{{ include_if_exists("reports/metrics/001.txt", single_line=0) }}
\ No newline at end of file
diff --git a/docs/metrics/general metrics/02_assertions.md b/docs/metrics/general metrics/02_assertions.md
new file mode 100644
index 0000000..dad41a5
--- /dev/null
+++ b/docs/metrics/general metrics/02_assertions.md	
@@ -0,0 +1,36 @@
+# 02 - Assertions in a graph
+
+This metric counts the total number of assertions (triples/edges) in an RDF graph. An assertion is any statement in the form of subject-predicate-object that exists in the graph. The assertion count provides a fundamental measure of the graph's connectivity and density.
+
+The metric helps to:
+
+- Measure the total number of relationships in the graph
+- Understand the graph's density
+- Track the growth of relationships over time
+- Compare connectivity between different graph versions
+
+## Queries
+
+### Count Total Number of Assertions
+
+```sparql
+{{ include_if_exists("queries/metrics/GM002_1.rq") }}
+```
+
+### Count Number of Entity-to-Entity Assertions
+
+```sparql
+{{ include_if_exists("queries/metrics/GM002_2.rq") }}
+```
+
+### Count Number of Entity-to-Literal Assertions
+
+```sparql
+{{ include_if_exists("queries/metrics/GM002_3.rq") }}
+```
+
+## Results
+{{ include_if_exists("reports/metrics/002_1.txt", start_line=1) }}
+
+Last execution:
+{{ include_if_exists("reports/metrics/002_1.txt", single_line=0) }}
\ No newline at end of file
diff --git a/docs/metrics/general metrics/03_linkage_degree.md b/docs/metrics/general metrics/03_linkage_degree.md
new file mode 100644
index 0000000..e526e1e
--- /dev/null
+++ b/docs/metrics/general metrics/03_linkage_degree.md	
@@ -0,0 +1,57 @@
+# 03 - Linkage Degree Analysis
+
+This metric analyzes the connectivity patterns in the graph by measuring the linkage degree of entities. The linkage degree represents how well entities are connected to other entities through relationships. It helps understand the graph's structural characteristics and identifies patterns of connectivity.
+
+The metric provides insights into:
+
+- Average number of relationships per entity
+- Distribution of connections across the graph
+- Identification of highly connected or isolated entities
+- Overall graph connectivity patterns
+
+## Queries
+
+### Average Linkage Degree
+
+This query calculates the average number of relationships (both incoming and outgoing) per entity in the graph.
+
+```sparql
+{{ include_if_exists("queries/metrics/GM003_1.rq") }}
+```
+
+??? warning "Batch Processing Limitation"
+    The following queries use batch processing with a limitation of 1000 entities. While this enables quick results, it leads to approximate values. The results only approach the actual average value when using batch sizes in the six-figure range.
+
+    For precise analysis, either adjust the LIMIT value accordingly or use the non-batch version.
+
+### Average Linkage Degree (Batch Processing)
+
+This is an optimized version of the linkage degree calculation that uses batch processing. It's particularly useful for large datasets where the standard query might timeout or consume too many resources. The query processes a limited number of entities at a time.
+
+```sparql
+{{ include_if_exists("queries/metrics/GM003_2.rq") }}
+```
+
+### Average Outgoing Linkage Degree (Batch Processing)
+
+This query focuses specifically on outgoing relationships, calculating the average number of outgoing edges per entity. It uses batch processing for efficient execution on larger datasets.
+
+```sparql
+{{ include_if_exists("queries/metrics/GM003_3.rq") }}
+```
+
+### Average Incoming Linkage Degree (Batch Processing)
+
+This query calculates the average number of incoming relationships per entity, providing insights into how frequently entities are referenced by others in the graph. It also uses batch processing for efficiency.
+
+```sparql
+{{ include_if_exists("queries/metrics/GM003_4.rq") }}
+```
+
+## Results
+
+### Average Linkage Degree
+{{ include_if_exists("reports/metrics/003_1.txt", start_line=1) }}
+
+Last execution:
+{{ include_if_exists("reports/metrics/003_1.txt", single_line=0) }}
\ No newline at end of file
diff --git a/docs/metrics/04_outgoing_edges.md b/docs/metrics/general metrics/04_outgoing_edges.md
similarity index 55%
rename from docs/metrics/04_outgoing_edges.md
rename to docs/metrics/general metrics/04_outgoing_edges.md
index 8634ee7..42dc5f4 100644
--- a/docs/metrics/04_outgoing_edges.md
+++ b/docs/metrics/general metrics/04_outgoing_edges.md	
@@ -1,4 +1,4 @@
-# Outgoing Edges
+# 04 - Outgoing Edges
 
 This metric determines the median number of outgoing edges across all nodes in the graph. The calculation requires multiple steps.
 
@@ -7,13 +7,13 @@ This metric determines the median number of outgoing edges across all nodes in t
 ### Step 1: Count Outgoing Edges Per Node
 
 ```sparql
-{{ include_if_exists("queries/metrics/GM0004_1.rq") }}
+{{ include_if_exists("queries/metrics/GM004_1.rq") }}
 ```
 
 ### Step 2: Get Total Number of Nodes
 
 ```sparql
-{{ include_if_exists("queries/metrics/GM0004_2.rq") }}
+{{ include_if_exists("queries/metrics/GM004_2.rq") }}
 ```
 
 ### Step 3: Calculate Median Position
@@ -21,17 +21,17 @@ This metric determines the median number of outgoing edges across all nodes in t
 Using the total node count (n), median position is: position = (n+1)/2
 
 ```sparql
-{{ include_if_exists("queries/metrics/GM0004_3.rq") }}
+{{ include_if_exists("queries/metrics/GM004_3.rq") }}
 ```
 
 ### Step 4: Get Median Value(s)
 
 ```sparql
-{{ include_if_exists("queries/metrics/GM0004_4.rq") }}
+{{ include_if_exists("queries/metrics/GM004_4.rq") }}
 ```
 
 ## Results
-{{ include_if_exists("reports/metrics/0004.txt", start_line=1) }}
+{{ include_if_exists("reports/metrics/004.txt", start_line=1) }}
 
 Last execution:
-{{ include_if_exists("reports/metrics/0004.txt", single_line=0) }}
\ No newline at end of file
+{{ include_if_exists("reports/metrics/004.txt", single_line=0) }}
\ No newline at end of file
diff --git a/docs/metrics/05_incoming_edges.md b/docs/metrics/general metrics/05_incoming_edges.md
similarity index 55%
rename from docs/metrics/05_incoming_edges.md
rename to docs/metrics/general metrics/05_incoming_edges.md
index f7670f9..a815f4c 100644
--- a/docs/metrics/05_incoming_edges.md
+++ b/docs/metrics/general metrics/05_incoming_edges.md	
@@ -1,4 +1,4 @@
-# Incoming Edges
+# 05 - Incoming Edges
 
 This metric determines the median number of incoming edges across all nodes in the graph. The calculation requires multiple steps.
 
@@ -7,13 +7,13 @@ This metric determines the median number of incoming edges across all nodes in t
 ### Step 1: Count Incoming Edges Per Node
 
 ```sparql
-{{ include_if_exists("queries/metrics/GM0005_1.rq") }}
+{{ include_if_exists("queries/metrics/GM005_1.rq") }}
 ```
 
 ### Step 2: Get Total Number of Nodes
 
 ```sparql
-{{ include_if_exists("queries/metrics/GM0005_2.rq") }}
+{{ include_if_exists("queries/metrics/GM005_2.rq") }}
 ```
 
 ### Step 3: Calculate Median Position
@@ -21,17 +21,17 @@ This metric determines the median number of incoming edges across all nodes in t
 Using the total node count (n), median position is: position = (n+1)/2
 
 ```sparql
-{{ include_if_exists("queries/metrics/GM0005_3.rq") }}
+{{ include_if_exists("queries/metrics/GM005_3.rq") }}
 ```
 
 ### Step 4: Get Median Value(s)
 
 ```sparql
-{{ include_if_exists("queries/metrics/GM0005_4.rq") }}
+{{ include_if_exists("queries/metrics/GM005_4.rq") }}
 ```
 
 ## Results
-{{ include_if_exists("reports/metrics/0005.txt", start_line=1) }}
+{{ include_if_exists("reports/metrics/005.txt", start_line=1) }}
 
 Last execution:
-{{ include_if_exists("reports/metrics/0005.txt", single_line=0) }}
+{{ include_if_exists("reports/metrics/005.txt", single_line=0) }}
diff --git a/docs/metrics/index.md b/docs/metrics/general metrics/index.md
similarity index 97%
rename from docs/metrics/index.md
rename to docs/metrics/general metrics/index.md
index 380ab61..b97d544 100644
--- a/docs/metrics/index.md
+++ b/docs/metrics/general metrics/index.md	
@@ -1,4 +1,4 @@
-# Metrics
+# Overview
 
 The KnowledgeGraph metrics provide quantitative insights into the structure and content of our knowledge graph. These measurements help us to:
 
diff --git a/queries/metrics/GM0004_1.rq b/queries/metrics/GM004_1.rq
similarity index 84%
rename from queries/metrics/GM0004_1.rq
rename to queries/metrics/GM004_1.rq
index 8a457d5..5c2ffa7 100644
--- a/queries/metrics/GM0004_1.rq
+++ b/queries/metrics/GM004_1.rq
@@ -5,4 +5,3 @@ WHERE {
   ?source ?p ?outgoing .
 }
 GROUP BY ?source
-ORDER BY DESC(?outEdges)
diff --git a/queries/metrics/GM0004_2.rq b/queries/metrics/GM004_2.rq
similarity index 100%
rename from queries/metrics/GM0004_2.rq
rename to queries/metrics/GM004_2.rq
diff --git a/queries/metrics/GM0004_3.rq b/queries/metrics/GM004_3.rq
similarity index 87%
rename from queries/metrics/GM0004_3.rq
rename to queries/metrics/GM004_3.rq
index 165999b..65391cc 100644
--- a/queries/metrics/GM0004_3.rq
+++ b/queries/metrics/GM004_3.rq
@@ -1,4 +1,4 @@
-# Returns a single number representing the median position of outgoing edges per node
+# Returns a single number representing the median position of outgoing edges
 
 SELECT (COUNT(DISTINCT ?source) as ?uniqueNodes) / 2
 WHERE {
diff --git a/queries/metrics/GM0004_4.rq b/queries/metrics/GM004_4.rq
similarity index 100%
rename from queries/metrics/GM0004_4.rq
rename to queries/metrics/GM004_4.rq
diff --git a/queries/metrics/GM0005_1.rq b/queries/metrics/GM005_1.rq
similarity index 85%
rename from queries/metrics/GM0005_1.rq
rename to queries/metrics/GM005_1.rq
index 86d531f..e4440f0 100644
--- a/queries/metrics/GM0005_1.rq
+++ b/queries/metrics/GM005_1.rq
@@ -5,3 +5,4 @@ WHERE {
   ?incoming ?p ?target .
 }
 GROUP BY ?target
+ORDER BY ASC(?inEdges)
diff --git a/queries/metrics/GM0005_2.rq b/queries/metrics/GM005_2.rq
similarity index 100%
rename from queries/metrics/GM0005_2.rq
rename to queries/metrics/GM005_2.rq
diff --git a/queries/metrics/GM0005_3.rq b/queries/metrics/GM005_3.rq
similarity index 100%
rename from queries/metrics/GM0005_3.rq
rename to queries/metrics/GM005_3.rq
diff --git a/queries/metrics/GM0005_4.rq b/queries/metrics/GM005_4.rq
similarity index 100%
rename from queries/metrics/GM0005_4.rq
rename to queries/metrics/GM005_4.rq
diff --git a/reports/metrics/0004.txt b/reports/metrics/004.txt
similarity index 100%
rename from reports/metrics/0004.txt
rename to reports/metrics/004.txt
diff --git a/reports/metrics/0005.txt b/reports/metrics/005.txt
similarity index 100%
rename from reports/metrics/0005.txt
rename to reports/metrics/005.txt
-- 
GitLab


From 598bc9c8033ec1bdab2541498c6284b598d4c99b Mon Sep 17 00:00:00 2001
From: Ralf Klammer <ralf.klammer@tu-dresden.de>
Date: Thu, 13 Mar 2025 10:42:08 +0100
Subject: [PATCH 04/59] [metrics] Integrate python-package 'kg_analysis'

---
 scripts/.flake8                     |   2 +
 scripts/.gitignore                  |   6 +
 scripts/LICENSE                     | 201 ++++++++++++++++++++++++++++
 scripts/MANIFEST.in                 |   0
 scripts/README.MD                   |  44 ++++++
 scripts/kg_analysis/__init__.py     |   3 +
 scripts/kg_analysis/cli.py          |  72 ++++++++++
 scripts/kg_analysis/query_runner.py |  43 ++++++
 scripts/kg_analysis/util.py         |  18 +++
 scripts/pyproject.toml              |  28 ++++
 10 files changed, 417 insertions(+)
 create mode 100644 scripts/.flake8
 create mode 100644 scripts/.gitignore
 create mode 100644 scripts/LICENSE
 create mode 100644 scripts/MANIFEST.in
 create mode 100644 scripts/README.MD
 create mode 100644 scripts/kg_analysis/__init__.py
 create mode 100644 scripts/kg_analysis/cli.py
 create mode 100644 scripts/kg_analysis/query_runner.py
 create mode 100644 scripts/kg_analysis/util.py
 create mode 100644 scripts/pyproject.toml

diff --git a/scripts/.flake8 b/scripts/.flake8
new file mode 100644
index 0000000..bfead2c
--- /dev/null
+++ b/scripts/.flake8
@@ -0,0 +1,2 @@
+[flake8]
+max-line-length = 79
diff --git a/scripts/.gitignore b/scripts/.gitignore
new file mode 100644
index 0000000..154d56a
--- /dev/null
+++ b/scripts/.gitignore
@@ -0,0 +1,6 @@
+**/__pycache__
+**/dist
+
+*.egg-info
+*.pyc
+*.ini
diff --git a/scripts/LICENSE b/scripts/LICENSE
new file mode 100644
index 0000000..d577725
--- /dev/null
+++ b/scripts/LICENSE
@@ -0,0 +1,201 @@
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+
+   APPENDIX: How to apply the Apache License to your work.
+
+      To apply the Apache License to your work, attach the following
+      boilerplate notice, with the fields enclosed by brackets "[]"
+      replaced with your own identifying information. (Don't include
+      the brackets!)  The text should be enclosed in the appropriate
+      comment syntax for the file format. We also recommend that a
+      file or class name and description of purpose be included on the
+      same "printed page" as the copyright notice for easier
+      identification within third-party archives.
+
+   Copyright 2025 NFDI4Earth / SoftwareAndArchitecture
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
diff --git a/scripts/MANIFEST.in b/scripts/MANIFEST.in
new file mode 100644
index 0000000..e69de29
diff --git a/scripts/README.MD b/scripts/README.MD
new file mode 100644
index 0000000..bc7066b
--- /dev/null
+++ b/scripts/README.MD
@@ -0,0 +1,44 @@
+# KG Analysis Tool
+
+A command-line tool for analyzing SPARQL endpoints, specifically designed for the NFDI4Earth Knowledge Graph.
+
+## Features
+
+- Run SPARQL queries from files
+- Save query results to output files
+- Configurable SPARQL endpoint via environment variable
+
+## Installation
+
+```bash
+# Create and activate virtual environment
+python3 -m venv venv
+source venv/bin/activate
+
+# Install in development mode
+pip install -e .
+```
+
+## Usage
+
+Set the SPARQL endpoint (optional):
+```bash
+export KG_SPARQL_ENDPOINT="https://your-sparql-endpoint/sparql"
+```
+
+Run a query:
+```bash
+kg_analysis run-query -q path/to/query.rq -o path/to/output.txt
+```
+
+## Requirements
+
+- Python >= 3.10
+- click
+- SPARQLWrapper
+
+## License
+
+This project is published under the Apache License 2.0, see file `LICENSE`.
+
+Contributors: Ralf Klammer
diff --git a/scripts/kg_analysis/__init__.py b/scripts/kg_analysis/__init__.py
new file mode 100644
index 0000000..a9463af
--- /dev/null
+++ b/scripts/kg_analysis/__init__.py
@@ -0,0 +1,3 @@
+from rich.console import Console
+
+console = Console()
diff --git a/scripts/kg_analysis/cli.py b/scripts/kg_analysis/cli.py
new file mode 100644
index 0000000..1808306
--- /dev/null
+++ b/scripts/kg_analysis/cli.py
@@ -0,0 +1,72 @@
+# -*- coding: utf-8 -*-
+# Copyright (C) 2025 TU-Dresden, ZIH
+# ralf.klammer@tu-dresden.de
+import logging
+
+import click
+import os
+
+from pathlib import Path
+from rich import print_json
+
+from .util import cli_startup
+from .query_runner import QueryRunner  # type: ignore
+
+log = logging.getLogger(__name__)
+
+SPARQL_ENDPOINT = os.getenv(
+    "SPARQL_ENDPOINT", "https://sparql.knowledgehub.nfdi4earth.de"
+)
+
+
+@click.group()
+@click.option("--debug/--no-debug", "-d", is_flag=True, default=False)
+@click.pass_context
+def main(ctx, debug):
+    cli_startup(log_level=debug and logging.DEBUG or logging.INFO)
+    ctx.ensure_object(dict)
+    ctx.obj["DEBUG"] = debug
+
+
+@main.command()
+@click.option(
+    "--query",
+    "-q",
+    type=click.Path(exists=True),
+    help="Path to SPARQL query file",
+    required=True,
+)
+@click.option("--output", "-o", type=click.Path(), help="Path to save results")
+@click.pass_context
+def run_query(ctx, query, output):
+    """Run analysis on the KnowledgeGraph."""
+    runner = QueryRunner(SPARQL_ENDPOINT)
+
+    query_path = Path(query)
+    # if output:
+    #     output_path = Path(output)
+    # else:
+    #     # Create default output path: queries/metrics/GM00X.rq
+    #     #   -> reports/metrics/00X.txt
+    #     relative_path = query_path.relative_to("queries")
+    #     output_path = Path("reports").joinpath(
+    #         relative_path.with_suffix(".txt")
+    #     )
+    try:
+        results = runner.run_metric(
+            query_path, output_path=Path(output) if output else None
+        )
+        click.echo(
+            click.style("Query executed successfully: ", fg="green")
+            + click.style(query_path.name, fg="blue")
+        )
+        if not output:
+            print_json(data=results)
+    except Exception as e:
+        click.echo(
+            click.style(f"Error executing query: {e}", fg="red"), err=True
+        )
+
+
+if __name__ == "__main__":
+    main(obj={})
diff --git a/scripts/kg_analysis/query_runner.py b/scripts/kg_analysis/query_runner.py
new file mode 100644
index 0000000..d09812f
--- /dev/null
+++ b/scripts/kg_analysis/query_runner.py
@@ -0,0 +1,43 @@
+# -*- coding: utf-8 -*-
+# Copyright (C) 2025 TU-Dresden, ZIH
+# ralf.klammer@tu-dresden.de
+import logging
+
+from pathlib import Path
+from typing import Optional
+
+from SPARQLWrapper import JSON, SPARQLWrapper  # type: ignore
+
+log = logging.getLogger(__name__)
+
+
+class QueryRunner:
+    def __init__(self, endpoint_url: str):
+        self.sparql = SPARQLWrapper(endpoint_url)
+        self.sparql.setReturnFormat(JSON)
+
+    def execute_query(self, query_path: Path) -> dict:
+        """Execute a SPARQL query from a file and return the results."""
+        with open(query_path, "r") as f:
+            query = f.read()
+
+        self.sparql.setQuery(query)
+        return self.sparql.query().convert()
+
+    def run_metric(
+        self, query_path: Path, output_path: Optional[Path] = None
+    ) -> dict:
+        """Run a metric query and optionally save the results."""
+        results = self.execute_query(query_path)
+
+        if output_path:
+            output_path.parent.mkdir(parents=True, exist_ok=True)
+            with open(output_path, "w") as f:
+                # Extract first value from results as metrics are usually
+                # single values
+                value = list(results["results"]["bindings"][0].values())[0][
+                    "value"
+                ]
+                f.write(str(value))
+
+        return results
diff --git a/scripts/kg_analysis/util.py b/scripts/kg_analysis/util.py
new file mode 100644
index 0000000..722ac16
--- /dev/null
+++ b/scripts/kg_analysis/util.py
@@ -0,0 +1,18 @@
+# -*- coding: utf-8 -*-
+# Copyright (C) 2025 TU-Dresden, ZIH
+# ralf.klammer@tu-dresden.de
+import logging
+
+log = logging.getLogger(__name__)
+
+
+def cli_startup(log_level=logging.INFO, log_file=None):
+    log_config = dict(
+        level=log_level,
+        format="%(asctime)s %(name)-10s %(levelname)-4s %(message)s",
+    )
+    if log_file:
+        log_config["filename"] = log_file
+
+    logging.basicConfig(**log_config)
+    logging.getLogger("").setLevel(log_level)
diff --git a/scripts/pyproject.toml b/scripts/pyproject.toml
new file mode 100644
index 0000000..181b2ec
--- /dev/null
+++ b/scripts/pyproject.toml
@@ -0,0 +1,28 @@
+[build-system]
+requires = ["setuptools>=61.0"]
+build-backend = "setuptools.build_meta"
+
+[project]
+name = "kg_analysis"
+version = "1.0.0b1"
+description = "Knowledge Graph Analysis"
+authors = [
+    { name = "Ralf Klammer", email = "ralf.klammer@tu-dresden.de" }
+]
+requires-python = ">=3.10"
+license = { text = "Copyright (C) 2025 TU-Dresden, ZIH" }
+dependencies = [
+    "rdflib",
+    "SPARQLWrapper",
+    "click",
+    "rich",
+]
+
+[project.scripts]
+kg_analysis = "kg_analysis.cli:main"
+
+[tool.black]
+line-length = 79
+
+[tool.mypy]
+warn_no_return = false
-- 
GitLab


From c403969fc5cb675618d77c357341cf9b861e31c1 Mon Sep 17 00:00:00 2001
From: Ralf Klammer <ralf.klammer@tu-dresden.de>
Date: Thu, 13 Mar 2025 14:01:21 +0100
Subject: [PATCH 05/59] [metrics] First version of fully automated generation
 of metrics

---
 docs/metrics/general metrics/01_instances.md  |   4 +-
 docs/metrics/general metrics/02_assertions.md |   4 +-
 .../general metrics/03_linkage_degree.md      |   4 +-
 .../general metrics/04_outgoing_edges.md      |   4 +-
 .../general metrics/05_incoming_edges.md      |   4 +-
 reports/metrics/GM001.txt                     |   3 +
 reports/metrics/GM002.txt                     |   3 +
 reports/metrics/GM003.txt                     |   3 +
 reports/metrics/GM004.txt                     |   4 +
 reports/metrics/GM005.txt                     |   4 +
 scripts/kg_analysis/__init__.py               |   9 +
 scripts/kg_analysis/cli.py                    |  24 +--
 scripts/kg_analysis/metrics_runner.py         | 169 ++++++++++++++++++
 scripts/kg_analysis/query_runner.py           |  57 +++---
 14 files changed, 247 insertions(+), 49 deletions(-)
 create mode 100644 reports/metrics/GM001.txt
 create mode 100644 reports/metrics/GM002.txt
 create mode 100644 reports/metrics/GM003.txt
 create mode 100644 reports/metrics/GM004.txt
 create mode 100644 reports/metrics/GM005.txt
 create mode 100644 scripts/kg_analysis/metrics_runner.py

diff --git a/docs/metrics/general metrics/01_instances.md b/docs/metrics/general metrics/01_instances.md
index 001bbf2..a869138 100644
--- a/docs/metrics/general metrics/01_instances.md	
+++ b/docs/metrics/general metrics/01_instances.md	
@@ -18,7 +18,7 @@ The metric helps to:
 ```
 
 ## Results
-{{ include_if_exists("reports/metrics/001.txt", start_line=1) }}
+{{ include_if_exists("reports/metrics/GM001.txt", start_line=1) }}
 
 Last execution:
-{{ include_if_exists("reports/metrics/001.txt", single_line=0) }}
\ No newline at end of file
+{{ include_if_exists("reports/metrics/GM001.txt", single_line=0) }}
\ No newline at end of file
diff --git a/docs/metrics/general metrics/02_assertions.md b/docs/metrics/general metrics/02_assertions.md
index dad41a5..c2d3927 100644
--- a/docs/metrics/general metrics/02_assertions.md	
+++ b/docs/metrics/general metrics/02_assertions.md	
@@ -30,7 +30,7 @@ The metric helps to:
 ```
 
 ## Results
-{{ include_if_exists("reports/metrics/002_1.txt", start_line=1) }}
+{{ include_if_exists("reports/metrics/GM002.txt", start_line=1) }}
 
 Last execution:
-{{ include_if_exists("reports/metrics/002_1.txt", single_line=0) }}
\ No newline at end of file
+{{ include_if_exists("reports/metrics/GM002.txt", single_line=0) }}
\ No newline at end of file
diff --git a/docs/metrics/general metrics/03_linkage_degree.md b/docs/metrics/general metrics/03_linkage_degree.md
index e526e1e..ebfd0ee 100644
--- a/docs/metrics/general metrics/03_linkage_degree.md	
+++ b/docs/metrics/general metrics/03_linkage_degree.md	
@@ -51,7 +51,7 @@ This query calculates the average number of incoming relationships per entity, p
 ## Results
 
 ### Average Linkage Degree
-{{ include_if_exists("reports/metrics/003_1.txt", start_line=1) }}
+{{ include_if_exists("reports/metrics/GM003.txt", start_line=1) }}
 
 Last execution:
-{{ include_if_exists("reports/metrics/003_1.txt", single_line=0) }}
\ No newline at end of file
+{{ include_if_exists("reports/metrics/GM003.txt", single_line=0) }}
\ No newline at end of file
diff --git a/docs/metrics/general metrics/04_outgoing_edges.md b/docs/metrics/general metrics/04_outgoing_edges.md
index 42dc5f4..0a80101 100644
--- a/docs/metrics/general metrics/04_outgoing_edges.md	
+++ b/docs/metrics/general metrics/04_outgoing_edges.md	
@@ -31,7 +31,7 @@ Using the total node count (n), median position is: position = (n+1)/2
 ```
 
 ## Results
-{{ include_if_exists("reports/metrics/004.txt", start_line=1) }}
+{{ include_if_exists("reports/metrics/GM004.txt", start_line=1) }}
 
 Last execution:
-{{ include_if_exists("reports/metrics/004.txt", single_line=0) }}
\ No newline at end of file
+{{ include_if_exists("reports/metrics/GM004.txt", single_line=0) }}
\ No newline at end of file
diff --git a/docs/metrics/general metrics/05_incoming_edges.md b/docs/metrics/general metrics/05_incoming_edges.md
index a815f4c..63363aa 100644
--- a/docs/metrics/general metrics/05_incoming_edges.md	
+++ b/docs/metrics/general metrics/05_incoming_edges.md	
@@ -31,7 +31,7 @@ Using the total node count (n), median position is: position = (n+1)/2
 ```
 
 ## Results
-{{ include_if_exists("reports/metrics/005.txt", start_line=1) }}
+{{ include_if_exists("reports/metrics/GM005.txt", start_line=1) }}
 
 Last execution:
-{{ include_if_exists("reports/metrics/005.txt", single_line=0) }}
+{{ include_if_exists("reports/metrics/GM005.txt", single_line=0) }}
diff --git a/reports/metrics/GM001.txt b/reports/metrics/GM001.txt
new file mode 100644
index 0000000..4778d12
--- /dev/null
+++ b/reports/metrics/GM001.txt
@@ -0,0 +1,3 @@
+2025-03-13T13:57
+- Instance count: 1252089
+- Execution time: 0.05 seconds
\ No newline at end of file
diff --git a/reports/metrics/GM002.txt b/reports/metrics/GM002.txt
new file mode 100644
index 0000000..f83346c
--- /dev/null
+++ b/reports/metrics/GM002.txt
@@ -0,0 +1,3 @@
+2025-03-13T13:57
+- Assertions count: 40786353
+- Execution time: 0.04 seconds
\ No newline at end of file
diff --git a/reports/metrics/GM003.txt b/reports/metrics/GM003.txt
new file mode 100644
index 0000000..4680c25
--- /dev/null
+++ b/reports/metrics/GM003.txt
@@ -0,0 +1,3 @@
+2025-03-13T13:57
+- Assertions count: 3.894342748611638
+- Execution time: 0.01 seconds
\ No newline at end of file
diff --git a/reports/metrics/GM004.txt b/reports/metrics/GM004.txt
new file mode 100644
index 0000000..7dc2b50
--- /dev/null
+++ b/reports/metrics/GM004.txt
@@ -0,0 +1,4 @@
+2025-03-13T13:57
+- Total count: 6705836
+- Median: None
+- Execution time: 12.72 seconds
\ No newline at end of file
diff --git a/reports/metrics/GM005.txt b/reports/metrics/GM005.txt
new file mode 100644
index 0000000..2c28f94
--- /dev/null
+++ b/reports/metrics/GM005.txt
@@ -0,0 +1,4 @@
+2025-03-13T13:57
+- Total count: 15111836
+- Median: 1
+- Execution time: 0.02 seconds
\ No newline at end of file
diff --git a/scripts/kg_analysis/__init__.py b/scripts/kg_analysis/__init__.py
index a9463af..ae88787 100644
--- a/scripts/kg_analysis/__init__.py
+++ b/scripts/kg_analysis/__init__.py
@@ -1,3 +1,12 @@
+import os
+
 from rich.console import Console
+from rich import inspect
+from rich import print as rprint
 
 console = Console()
+
+SPARQL_ENDPOINT = os.getenv(
+    "SPARQL_ENDPOINT", "https://sparql.knowledgehub.nfdi4earth.de"
+)
+SPARQL_TIMEOUT = int(os.getenv("SPARQL_TIMEOUT", 120))
diff --git a/scripts/kg_analysis/cli.py b/scripts/kg_analysis/cli.py
index 1808306..989caf5 100644
--- a/scripts/kg_analysis/cli.py
+++ b/scripts/kg_analysis/cli.py
@@ -4,20 +4,16 @@
 import logging
 
 import click
-import os
 
 from pathlib import Path
 from rich import print_json
 
 from .util import cli_startup
 from .query_runner import QueryRunner  # type: ignore
+from .metrics_runner import MetricsRunner  # type: ignore
 
 log = logging.getLogger(__name__)
 
-SPARQL_ENDPOINT = os.getenv(
-    "SPARQL_ENDPOINT", "https://sparql.knowledgehub.nfdi4earth.de"
-)
-
 
 @click.group()
 @click.option("--debug/--no-debug", "-d", is_flag=True, default=False)
@@ -38,20 +34,11 @@ def main(ctx, debug):
 )
 @click.option("--output", "-o", type=click.Path(), help="Path to save results")
 @click.pass_context
-def run_query(ctx, query, output):
+def query(ctx, query, output):
     """Run analysis on the KnowledgeGraph."""
-    runner = QueryRunner(SPARQL_ENDPOINT)
+    runner = QueryRunner()
 
     query_path = Path(query)
-    # if output:
-    #     output_path = Path(output)
-    # else:
-    #     # Create default output path: queries/metrics/GM00X.rq
-    #     #   -> reports/metrics/00X.txt
-    #     relative_path = query_path.relative_to("queries")
-    #     output_path = Path("reports").joinpath(
-    #         relative_path.with_suffix(".txt")
-    #     )
     try:
         results = runner.run_metric(
             query_path, output_path=Path(output) if output else None
@@ -68,5 +55,10 @@ def run_query(ctx, query, output):
         )
 
 
+@main.command()
+def metrics():
+    MetricsRunner().run()
+
+
 if __name__ == "__main__":
     main(obj={})
diff --git a/scripts/kg_analysis/metrics_runner.py b/scripts/kg_analysis/metrics_runner.py
new file mode 100644
index 0000000..bd602e9
--- /dev/null
+++ b/scripts/kg_analysis/metrics_runner.py
@@ -0,0 +1,169 @@
+# -*- coding: utf-8 -*-
+# Copyright (C) 2025 TU-Dresden, ZIH
+# ralf.klammer@tu-dresden.de
+import logging
+
+from time import time
+
+from abc import ABC, abstractmethod
+from datetime import datetime
+from pathlib import Path
+
+from typing import Optional
+
+from .query_runner import QueryRunner
+
+log = logging.getLogger(__name__)
+
+
+class MetricsRunnerBase(ABC):
+    """Base class for metric runners."""
+
+    _query_file: Optional[str] = None
+    _output_file: Optional[str] = None
+    _execution_time: Optional[float] = None
+    fail: bool = False
+
+    def __init__(self):
+        self.runner = QueryRunner()
+        self.base_query_path = Path("queries/metrics")
+        self.base_output_path = Path("reports/metrics")
+        log.info(f"Running metric: {self.__class__.__name__}")
+
+    @property
+    def query_file(self):
+        return Path(self._query_file)
+
+    def get_query_path(self, query_file: str) -> Path:
+        return self.base_query_path.joinpath(query_file)
+
+    @property
+    def query_path(self):
+        return self.get_query_path(self.query_file)
+
+    @property
+    def output_path(self):
+        filename = self._output_file or self.query_file.with_suffix(".txt")
+        return self.base_output_path.joinpath(filename)
+
+    def query_metric(self, query_path: Path, **kwargs) -> Optional[int]:
+        """Run a metric query and the result"""
+        start_time = time()
+        result = None
+        try:
+            result = self.runner.execute_query(query_path, **kwargs)
+        except Exception as e:
+            log.error(f"Failed to run metric: {e}")
+            self.fail = True
+        self._execution_time = time() - start_time
+        if result and result["results"]["bindings"]:
+            return list(result["results"]["bindings"][0].values())[0]["value"]
+        return None
+
+    @abstractmethod
+    def run(self) -> dict:
+        """Run this specific metric."""
+        pass
+
+    def save(self, result) -> None:
+        """Run a metric query and optionally save the results."""
+
+        if self.fail:
+            log.error(f"Failed to run metric: {self.__class__.__name__}")
+
+        with open(self.output_path, "w") as f:
+            f.write(f"{datetime.now().isoformat(timespec="minutes")}\n")
+            f.write(str(result))
+            if self._execution_time:
+                f.write(
+                    f"\n- Execution time: {self._execution_time:.2f} seconds"
+                )
+            log.info(f"Results saved to {self.output_path}")
+
+
+class MetricsRunner_001(MetricsRunnerBase):
+    """Runner for Metric 001: Instance Count"""
+
+    _query_file = "GM001.rq"
+
+    def run(self):
+        result = self.query_metric(self.query_path)
+        result_text = f"- Instance count: {result}"
+        self.save(result_text)
+
+
+class MetricsRunner_002(MetricsRunnerBase):
+    """Runner for Metric 002: Assertions Count"""
+
+    _query_file = "GM002_1.rq"
+    _output_file = "GM002.txt"
+
+    def run(self):
+        result = self.query_metric(self.query_path)
+        result_text = f"- Assertions count: {result}"
+        self.save(result_text)
+
+
+class MetricsRunner_003(MetricsRunnerBase):
+    """Runner for Metric 003: Average Linkage Degree"""
+
+    _query_file = "GM003_1.rq"
+    _output_file = "GM003.txt"
+
+    def run(self):
+        result = self.query_metric(self.query_path)
+        result_text = f"- Assertions count: {result}"
+        self.save(result_text)
+
+
+class MetricsRunner_Edges(MetricsRunnerBase):
+    """Runner for Metric 004 & 005: Outgoing & Incoming Edges"""
+
+    _files = {
+        "outgoing": {
+            "outfile": "GM004.txt",
+            "total_query": "GM004_2.rq",
+            "median_query": "GM004_4.rq",
+        },
+        "incoming": {
+            "outfile": "GM005.txt",
+            "total_query": "GM005_2.rq",
+            "median_query": "GM005_4.rq",
+        },
+    }
+
+    def __init__(self, _type: str, *args, **kwargs):
+        super().__init__(*args, **kwargs)
+        self._output_file = self._files[_type]["outfile"]
+        self.total_query = self._files[_type]["total_query"]
+        self.median_query = self._files[_type]["median_query"]
+
+    def run(self):
+        edges_total = self.query_metric(self.get_query_path(self.total_query))
+
+        if not edges_total:
+            self.fail = True
+            return
+
+        edges_total = int(edges_total)
+        if edges_total % 2:
+            log.warning("Odd number of edges, median is not unique")
+        median_position = int(edges_total / 2)
+        median = self.query_metric(
+            self.get_query_path(self.median_query),
+            replace_dict={"{median_position}": median_position},
+        )
+
+        result_text = f"- Total count: {edges_total}"
+        result_text += f"\n- Median: {median}"
+        self.save(result_text)
+
+
+class MetricsRunner(ABC):
+
+    def run(self):
+        MetricsRunner_001().run()
+        MetricsRunner_002().run()
+        MetricsRunner_003().run()
+        MetricsRunner_Edges("outgoing").run()
+        MetricsRunner_Edges("incoming").run()
diff --git a/scripts/kg_analysis/query_runner.py b/scripts/kg_analysis/query_runner.py
index d09812f..9978138 100644
--- a/scripts/kg_analysis/query_runner.py
+++ b/scripts/kg_analysis/query_runner.py
@@ -3,41 +3,52 @@
 # ralf.klammer@tu-dresden.de
 import logging
 
+import time
+
 from pathlib import Path
-from typing import Optional
+from typing import Optional, Tuple
 
 from SPARQLWrapper import JSON, SPARQLWrapper  # type: ignore
 
+from . import SPARQL_ENDPOINT, SPARQL_TIMEOUT
+
 log = logging.getLogger(__name__)
 
 
 class QueryRunner:
-    def __init__(self, endpoint_url: str):
-        self.sparql = SPARQLWrapper(endpoint_url)
+    def __init__(self, timeout: int = SPARQL_TIMEOUT):
+        self.sparql = SPARQLWrapper(SPARQL_ENDPOINT)
         self.sparql.setReturnFormat(JSON)
+        # Set timeout in seconds and convert to milliseconds
+        # as required by SPARQLWrapper.setTimout()
+        self.sparql.setTimeout(timeout * 1000)
 
-    def execute_query(self, query_path: Path) -> dict:
-        """Execute a SPARQL query from a file and return the results."""
+    def execute_query(
+        self, query_path: Path, replace_dict: Optional[dict] = None
+    ) -> dict:
+        """Execute a SPARQL query from a file and return the json results"""
+        # Read the query from the file
         with open(query_path, "r") as f:
             query = f.read()
 
+        # Replace placeholders in the query
+        if replace_dict:
+            for r_key, r_value in replace_dict.items():
+                query = query.replace(r_key, str(r_value))
+
+        # Set the query and execute
         self.sparql.setQuery(query)
-        return self.sparql.query().convert()
+        result = self.sparql.query().convert()
 
-    def run_metric(
-        self, query_path: Path, output_path: Optional[Path] = None
-    ) -> dict:
-        """Run a metric query and optionally save the results."""
-        results = self.execute_query(query_path)
-
-        if output_path:
-            output_path.parent.mkdir(parents=True, exist_ok=True)
-            with open(output_path, "w") as f:
-                # Extract first value from results as metrics are usually
-                # single values
-                value = list(results["results"]["bindings"][0].values())[0][
-                    "value"
-                ]
-                f.write(str(value))
-
-        return results
+        # Check if the result is a dictionary
+        if isinstance(result, dict):
+            return result
+        raise ValueError("Query did not return a dictionary")
+
+    def run_metric(self, query_path: Path) -> Optional[int]:
+        """Run a metric query and the result"""
+        result = self.execute_query(query_path)
+        if result and result["results"]["bindings"]:
+            return list(result["results"]["bindings"][0].values())[0]["value"]
+
+        return None
-- 
GitLab


From 1d8a37b6f27f89fac17de710cf5bd5b475658b73 Mon Sep 17 00:00:00 2001
From: Ralf Klammer <ralf.klammer@tu-dresden.de>
Date: Thu, 13 Mar 2025 15:26:28 +0100
Subject: [PATCH 06/59] [metrics] Add min/max for edges

---
 .../general metrics/04_outgoing_edges.md      | 12 +++++++
 .../general metrics/05_incoming_edges.md      | 14 +++++++-
 queries/metrics/GM004_5.rq                    |  9 +++++
 queries/metrics/GM004_6.rq                    |  9 +++++
 queries/metrics/GM005_5.rq                    |  9 +++++
 queries/metrics/GM005_6.rq                    |  9 +++++
 scripts/kg_analysis/metrics_runner.py         | 33 +++++++++++++------
 7 files changed, 84 insertions(+), 11 deletions(-)
 create mode 100644 queries/metrics/GM004_5.rq
 create mode 100644 queries/metrics/GM004_6.rq
 create mode 100644 queries/metrics/GM005_5.rq
 create mode 100644 queries/metrics/GM005_6.rq

diff --git a/docs/metrics/general metrics/04_outgoing_edges.md b/docs/metrics/general metrics/04_outgoing_edges.md
index 0a80101..5a4afb4 100644
--- a/docs/metrics/general metrics/04_outgoing_edges.md	
+++ b/docs/metrics/general metrics/04_outgoing_edges.md	
@@ -30,6 +30,18 @@ Using the total node count (n), median position is: position = (n+1)/2
 {{ include_if_exists("queries/metrics/GM004_4.rq") }}
 ```
 
+### Step 5: Get Minimum Value
+
+```sparql
+{{ include_if_exists("queries/metrics/GM004_5.rq") }}
+```
+
+### Step 5: Get Maximum Value
+
+```sparql
+{{ include_if_exists("queries/metrics/GM004_6.rq") }}
+```
+
 ## Results
 {{ include_if_exists("reports/metrics/GM004.txt", start_line=1) }}
 
diff --git a/docs/metrics/general metrics/05_incoming_edges.md b/docs/metrics/general metrics/05_incoming_edges.md
index 63363aa..79fc98b 100644
--- a/docs/metrics/general metrics/05_incoming_edges.md	
+++ b/docs/metrics/general metrics/05_incoming_edges.md	
@@ -24,12 +24,24 @@ Using the total node count (n), median position is: position = (n+1)/2
 {{ include_if_exists("queries/metrics/GM005_3.rq") }}
 ```
 
-### Step 4: Get Median Value(s)
+### Step 4: Get Median Value
 
 ```sparql
 {{ include_if_exists("queries/metrics/GM005_4.rq") }}
 ```
 
+### Step 5: Get Minimum Value
+
+```sparql
+{{ include_if_exists("queries/metrics/GM005_5.rq") }}
+```
+
+### Step 5: Get Maximum Value
+
+```sparql
+{{ include_if_exists("queries/metrics/GM005_6.rq") }}
+```
+
 ## Results
 {{ include_if_exists("reports/metrics/GM005.txt", start_line=1) }}
 
diff --git a/queries/metrics/GM004_5.rq b/queries/metrics/GM004_5.rq
new file mode 100644
index 0000000..1dcb762
--- /dev/null
+++ b/queries/metrics/GM004_5.rq
@@ -0,0 +1,9 @@
+# Query to find the node with the minimum of outgoing edges in the graph
+
+SELECT COUNT(?outgoing) as ?outEdges
+WHERE {
+  ?source ?p ?outgoing .
+}
+GROUP BY ?source
+ORDER BY ASC(?outEdges)
+LIMIT 1
\ No newline at end of file
diff --git a/queries/metrics/GM004_6.rq b/queries/metrics/GM004_6.rq
new file mode 100644
index 0000000..6105231
--- /dev/null
+++ b/queries/metrics/GM004_6.rq
@@ -0,0 +1,9 @@
+# Query to find the node with the maximum of outgoing edges in the graph
+
+SELECT COUNT(?outgoing) as ?outEdges
+WHERE {
+  ?source ?p ?outgoing .
+}
+GROUP BY ?source
+ORDER BY DESC(?outEdges)
+LIMIT 1
\ No newline at end of file
diff --git a/queries/metrics/GM005_5.rq b/queries/metrics/GM005_5.rq
new file mode 100644
index 0000000..7465b95
--- /dev/null
+++ b/queries/metrics/GM005_5.rq
@@ -0,0 +1,9 @@
+# Query to find the node with the minimum of incoming edges in the graph
+
+SELECT COUNT(?incoming) as ?inEdges
+WHERE {
+  ?incoming ?p ?target .
+}
+GROUP BY ?target
+ORDER BY ASC(?inEdges)
+LIMIT 1
diff --git a/queries/metrics/GM005_6.rq b/queries/metrics/GM005_6.rq
new file mode 100644
index 0000000..f01a1dc
--- /dev/null
+++ b/queries/metrics/GM005_6.rq
@@ -0,0 +1,9 @@
+# Query to find the node with the maximum of incoming edges in the graph
+
+SELECT COUNT(?incoming) as ?inEdges
+WHERE {
+  ?incoming ?p ?target .
+}
+GROUP BY ?target
+ORDER BY DESC(?inEdges)
+LIMIT 1
diff --git a/scripts/kg_analysis/metrics_runner.py b/scripts/kg_analysis/metrics_runner.py
index bd602e9..4fd7144 100644
--- a/scripts/kg_analysis/metrics_runner.py
+++ b/scripts/kg_analysis/metrics_runner.py
@@ -21,7 +21,7 @@ class MetricsRunnerBase(ABC):
 
     _query_file: Optional[str] = None
     _output_file: Optional[str] = None
-    _execution_time: Optional[float] = None
+    _execution_time: float = 0
     fail: bool = False
 
     def __init__(self):
@@ -55,7 +55,7 @@ class MetricsRunnerBase(ABC):
         except Exception as e:
             log.error(f"Failed to run metric: {e}")
             self.fail = True
-        self._execution_time = time() - start_time
+        self._execution_time += time() - start_time
         if result and result["results"]["bindings"]:
             return list(result["results"]["bindings"][0].values())[0]["value"]
         return None
@@ -124,22 +124,33 @@ class MetricsRunner_Edges(MetricsRunnerBase):
             "outfile": "GM004.txt",
             "total_query": "GM004_2.rq",
             "median_query": "GM004_4.rq",
+            "min_query": "GM004_5.rq",
+            "max_query": "GM004_6.rq",
         },
         "incoming": {
             "outfile": "GM005.txt",
             "total_query": "GM005_2.rq",
             "median_query": "GM005_4.rq",
+            "min_query": "GM005_5.rq",
+            "max_query": "GM005_6.rq",
         },
     }
 
     def __init__(self, _type: str, *args, **kwargs):
         super().__init__(*args, **kwargs)
+        self.type = _type
         self._output_file = self._files[_type]["outfile"]
-        self.total_query = self._files[_type]["total_query"]
-        self.median_query = self._files[_type]["median_query"]
 
     def run(self):
-        edges_total = self.query_metric(self.get_query_path(self.total_query))
+        edges_total = self.query_metric(
+            self.get_query_path(self._files[self.type]["total_query"])
+        )
+        minimum = self.query_metric(
+            self.get_query_path(self._files[self.type]["min_query"])
+        )
+        maximum = self.query_metric(
+            self.get_query_path(self._files[self.type]["max_query"])
+        )
 
         if not edges_total:
             self.fail = True
@@ -150,20 +161,22 @@ class MetricsRunner_Edges(MetricsRunnerBase):
             log.warning("Odd number of edges, median is not unique")
         median_position = int(edges_total / 2)
         median = self.query_metric(
-            self.get_query_path(self.median_query),
+            self.get_query_path(self._files[self.type]["median_query"]),
             replace_dict={"{median_position}": median_position},
         )
 
         result_text = f"- Total count: {edges_total}"
+        result_text += f"\n- Minimum: {minimum}"
         result_text += f"\n- Median: {median}"
+        result_text += f"\n- Maximum: {maximum}"
         self.save(result_text)
 
 
 class MetricsRunner(ABC):
 
     def run(self):
-        MetricsRunner_001().run()
-        MetricsRunner_002().run()
-        MetricsRunner_003().run()
-        MetricsRunner_Edges("outgoing").run()
+        # MetricsRunner_001().run()
+        # MetricsRunner_002().run()
+        # MetricsRunner_003().run()
+        # MetricsRunner_Edges("outgoing").run()
         MetricsRunner_Edges("incoming").run()
-- 
GitLab


From 96a8427c9068d739e65a4ae55f40a88ebad00ef0 Mon Sep 17 00:00:00 2001
From: Ralf Klammer <ralf.klammer@tu-dresden.de>
Date: Thu, 13 Mar 2025 15:26:50 +0100
Subject: [PATCH 07/59] [metrics] Move metrics/index.md

---
 docs/metrics/{general metrics => }/index.md | 0
 1 file changed, 0 insertions(+), 0 deletions(-)
 rename docs/metrics/{general metrics => }/index.md (100%)

diff --git a/docs/metrics/general metrics/index.md b/docs/metrics/index.md
similarity index 100%
rename from docs/metrics/general metrics/index.md
rename to docs/metrics/index.md
-- 
GitLab


From 1132458dac60200653f7c196d015b0bd0498d854 Mon Sep 17 00:00:00 2001
From: Ralf Klammer <ralf.klammer@tu-dresden.de>
Date: Fri, 14 Mar 2025 09:58:22 +0100
Subject: [PATCH 08/59] [metrics] Queries for resource-type 'Dataset'

---
 .../resource specific metrics/06_dataset.md   | 91 +++++++++++++++++++
 queries/metrics/RM001_assertions.rq           |  8 ++
 queries/metrics/RM001_connectivity.rq         | 19 ++++
 queries/metrics/RM001_in_edges_max.rq         | 10 ++
 queries/metrics/RM001_in_edges_median.rq      | 15 +++
 queries/metrics/RM001_in_edges_min.rq         | 10 ++
 queries/metrics/RM001_in_edges_total.rq       |  7 ++
 queries/metrics/RM001_instances.rq            | 13 +++
 queries/metrics/RM001_linkage.rq              | 30 ++++++
 queries/metrics/RM001_out_edges_max.rq        | 10 ++
 queries/metrics/RM001_out_edges_median.rq     | 15 +++
 queries/metrics/RM001_out_edges_min.rq        | 10 ++
 queries/metrics/RM001_out_edges_total.rq      |  7 ++
 13 files changed, 245 insertions(+)
 create mode 100644 docs/metrics/resource specific metrics/06_dataset.md
 create mode 100644 queries/metrics/RM001_assertions.rq
 create mode 100644 queries/metrics/RM001_connectivity.rq
 create mode 100644 queries/metrics/RM001_in_edges_max.rq
 create mode 100644 queries/metrics/RM001_in_edges_median.rq
 create mode 100644 queries/metrics/RM001_in_edges_min.rq
 create mode 100644 queries/metrics/RM001_in_edges_total.rq
 create mode 100644 queries/metrics/RM001_instances.rq
 create mode 100644 queries/metrics/RM001_linkage.rq
 create mode 100644 queries/metrics/RM001_out_edges_max.rq
 create mode 100644 queries/metrics/RM001_out_edges_median.rq
 create mode 100644 queries/metrics/RM001_out_edges_min.rq
 create mode 100644 queries/metrics/RM001_out_edges_total.rq

diff --git a/docs/metrics/resource specific metrics/06_dataset.md b/docs/metrics/resource specific metrics/06_dataset.md
new file mode 100644
index 0000000..cab4f21
--- /dev/null
+++ b/docs/metrics/resource specific metrics/06_dataset.md	
@@ -0,0 +1,91 @@
+# Dataset Resource Metrics
+
+This document analyzes specific metrics for resources of type `Dataset` in the knowledge graph.
+
+## Basic Metrics
+
+### Number of Datasets
+
+see also: [general metrics/instances](/metrics/general%20metrics/01_instances/)
+
+```sparql
+{{ include_if_exists("queries/metrics/RM001_instances.rq") }}
+```
+
+### Connectivity to other resources
+
+```sparql
+{{ include_if_exists("queries/metrics/RM001_connectivity.rq") }}
+```
+
+### Number of Assertions
+
+see also: [general metrics/assortions](/metrics/general%20metrics/02_assertions/)
+
+```sparql
+{{ include_if_exists("queries/metrics/RM001_assertions.rq") }}
+```
+
+### Average linkage
+
+see also: [general metrics/linkage](/metrics/general%20metrics/03_linkage_degree/)
+
+```sparql
+{{ include_if_exists("queries/metrics/RM001_linkage.rq") }}
+```
+
+### Outgoing Edges Statistics
+
+see also: [general metrics/outgoing edges](/metrics/general%20metrics/04_outgoing_edges/)
+
+#### Total outgoing edges
+
+```sparql
+{{ include_if_exists("queries/metrics/RM001_out_edges_total.rq") }}
+```
+
+#### Minimum outgoing edges
+
+```sparql
+{{ include_if_exists("queries/metrics/RM001_out_edges_min.rq") }}
+```
+
+#### Maximum outgoing edges
+
+```sparql
+{{ include_if_exists("queries/metrics/RM001_out_edges_max.rq") }}
+```
+
+#### Median outgoing edges
+
+```sparql
+{{ include_if_exists("queries/metrics/RM001_out_edges_median.rq") }}
+```
+
+### Incoming Edges Statistics
+
+see also: [general metrics/incoming edges](/metrics/general%20metrics/05_incoming_edges/)
+
+#### Total incoming edges
+
+```sparql
+{{ include_if_exists("queries/metrics/RM001_out_edges_total.rq") }}
+```
+
+#### Minimum incoming edges
+
+```sparql
+{{ include_if_exists("queries/metrics/RM001_out_edges_min.rq") }}
+```
+
+#### Maximum incoming edges
+
+```sparql
+{{ include_if_exists("queries/metrics/RM001_out_edges_max.rq") }}
+```
+
+#### Median incoming edges
+
+```sparql
+{{ include_if_exists("queries/metrics/RM001_out_edges_median.rq") }}
+```
diff --git a/queries/metrics/RM001_assertions.rq b/queries/metrics/RM001_assertions.rq
new file mode 100644
index 0000000..051632a
--- /dev/null
+++ b/queries/metrics/RM001_assertions.rq
@@ -0,0 +1,8 @@
+# Total number of assertions (for resource type: Dataset)
+
+PREFIX dcat: <http://www.w3.org/ns/dcat#>
+
+SELECT (COUNT(*) AS ?numAssertions)
+WHERE {
+  ?subject ?predicate dcat:Dataset .
+}
diff --git a/queries/metrics/RM001_connectivity.rq b/queries/metrics/RM001_connectivity.rq
new file mode 100644
index 0000000..1998eba
--- /dev/null
+++ b/queries/metrics/RM001_connectivity.rq
@@ -0,0 +1,19 @@
+# Analysis of total connected resources for datasets
+#
+# Explanation:
+# Counts the number of resources that are connected to datasets
+# This gives us a metric for dataset connectivity in the graph
+
+PREFIX dcat: <http://www.w3.org/ns/dcat#>
+PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>
+
+SELECT (COUNT(DISTINCT ?connected) as ?numConnectedResources)
+WHERE {
+  ?dataset a dcat:Dataset ;
+          ?property ?connected .
+
+  # Ensure connected resource is not a literal
+  FILTER(isIRI(?connected))
+  # Exclude self-references to datasets
+  FILTER(?connected != ?dataset)
+}
diff --git a/queries/metrics/RM001_in_edges_max.rq b/queries/metrics/RM001_in_edges_max.rq
new file mode 100644
index 0000000..1d9a825
--- /dev/null
+++ b/queries/metrics/RM001_in_edges_max.rq
@@ -0,0 +1,10 @@
+# This SPARQL query calculates the maximum out-edge for datasets only.
+
+SELECT ?target (COUNT(?incoming) as ?inEdges)
+WHERE {
+?target a <http://www.w3.org/ns/dcat#Dataset> .
+?incoming ?p ?target .
+}
+GROUP BY ?target
+ORDER BY DESC(?inEdges)
+LIMIT 1
diff --git a/queries/metrics/RM001_in_edges_median.rq b/queries/metrics/RM001_in_edges_median.rq
new file mode 100644
index 0000000..d2ea70f
--- /dev/null
+++ b/queries/metrics/RM001_in_edges_median.rq
@@ -0,0 +1,15 @@
+# This SPARQL query calculates the median in-edge for datasets only.
+# The placeholder {median_position} must be replaced with the actual position of the median.
+
+SELECT ?inEdges as ?inMedian
+WHERE {
+    SELECT ?target (COUNT(?incoming) as ?inEdges)
+    WHERE {
+    ?target a <http://www.w3.org/ns/dcat#Dataset> .
+    ?incoming ?p ?target .
+    }
+    GROUP BY ?target
+    ORDER BY ASC(?inEdges)
+}
+OFFSET {median_position}
+LIMIT 1
diff --git a/queries/metrics/RM001_in_edges_min.rq b/queries/metrics/RM001_in_edges_min.rq
new file mode 100644
index 0000000..f39e6cb
--- /dev/null
+++ b/queries/metrics/RM001_in_edges_min.rq
@@ -0,0 +1,10 @@
+# This SPARQL query calculates the minimum out-edge for datasets only.
+
+SELECT ?target (COUNT(?incoming) as ?inEdges)
+WHERE {
+?target a <http://www.w3.org/ns/dcat#Dataset> .
+?incoming ?p ?target .
+}
+GROUP BY ?target
+ORDER BY ASC(?inEdges)
+LIMIT 1
diff --git a/queries/metrics/RM001_in_edges_total.rq b/queries/metrics/RM001_in_edges_total.rq
new file mode 100644
index 0000000..27b3569
--- /dev/null
+++ b/queries/metrics/RM001_in_edges_total.rq
@@ -0,0 +1,7 @@
+# Returns a single number representing the cleaned count of unique incoming nodes in the graph.
+
+SELECT (COUNT(DISTINCT ?target) as ?uniqueEdges)
+WHERE {
+  ?incoming ?p ?target .
+  ?target a <http://www.w3.org/ns/dcat#Dataset> .
+}
\ No newline at end of file
diff --git a/queries/metrics/RM001_instances.rq b/queries/metrics/RM001_instances.rq
new file mode 100644
index 0000000..73a7b0c
--- /dev/null
+++ b/queries/metrics/RM001_instances.rq
@@ -0,0 +1,13 @@
+# Count all instances of type Dataset
+#
+# Explanation:
+# ?dataset a dcat:Dataset finds all resources that are of type Dataset
+# COUNT(DISTINCT ?dataset) counts unique dataset instances
+# We use DISTINCT to avoid counting duplicates if a dataset has multiple types
+
+PREFIX dcat: <http://www.w3.org/ns/dcat#>
+
+SELECT (COUNT(DISTINCT ?dataset) AS ?datasetCount)
+WHERE {
+  ?dataset a dcat:Dataset .
+}
diff --git a/queries/metrics/RM001_linkage.rq b/queries/metrics/RM001_linkage.rq
new file mode 100644
index 0000000..f379777
--- /dev/null
+++ b/queries/metrics/RM001_linkage.rq
@@ -0,0 +1,30 @@
+# The average linkage degree for datasets only
+# (i.e.: how many assertions per dataset does the graph contain)
+
+SELECT (AVG(?totalDegree) as ?avgLinkageDegree)
+WHERE {
+  {
+    SELECT ?entity ((?outDegree + ?inDegree) as ?totalDegree)
+    WHERE {
+      # Only consider entities that are datasets
+      ?entity a <http://www.w3.org/ns/dcat#Dataset> .
+
+      {
+        SELECT ?entity (COUNT(*) as ?outDegree)
+        WHERE {
+          ?entity a <http://www.w3.org/ns/dcat#Dataset> .
+          ?entity ?p ?o .
+        }
+        GROUP BY ?entity
+      }
+      {
+        SELECT ?entity (COUNT(*) as ?inDegree)
+        WHERE {
+          ?entity a <http://www.w3.org/ns/dcat#Dataset> .
+          ?s ?p ?entity .  # Hier zählen wir die Verbindungen, die auf das Dataset zeigen
+        }
+        GROUP BY ?entity
+      }
+    }
+  }
+}
diff --git a/queries/metrics/RM001_out_edges_max.rq b/queries/metrics/RM001_out_edges_max.rq
new file mode 100644
index 0000000..4503611
--- /dev/null
+++ b/queries/metrics/RM001_out_edges_max.rq
@@ -0,0 +1,10 @@
+# This SPARQL query calculates the maximum out-edge for datasets only.
+
+SELECT COUNT(?outgoing) as ?outEdges
+WHERE {
+?source a <http://www.w3.org/ns/dcat#Dataset> .
+?source ?p ?outgoing .
+}
+GROUP BY ?source
+ORDER BY DESC(?outEdges)
+LIMIT 1
\ No newline at end of file
diff --git a/queries/metrics/RM001_out_edges_median.rq b/queries/metrics/RM001_out_edges_median.rq
new file mode 100644
index 0000000..dd705cb
--- /dev/null
+++ b/queries/metrics/RM001_out_edges_median.rq
@@ -0,0 +1,15 @@
+# This SPARQL query calculates the median out-edge for datasets only.
+# The placeholder {median_position} must be replaced with the actual position of the median.
+
+SELECT ?outEdges as ?outMedian
+WHERE {
+  SELECT ?source (COUNT(?outgoing) as ?outEdges)
+  WHERE {
+    ?source a <http://www.w3.org/ns/dcat#Dataset> .
+    ?source ?p ?outgoing .
+  }
+  GROUP BY ?source
+  ORDER BY ASC(?outEdges)
+}
+OFFSET {median_position}
+LIMIT 1
\ No newline at end of file
diff --git a/queries/metrics/RM001_out_edges_min.rq b/queries/metrics/RM001_out_edges_min.rq
new file mode 100644
index 0000000..55bcfcf
--- /dev/null
+++ b/queries/metrics/RM001_out_edges_min.rq
@@ -0,0 +1,10 @@
+# This SPARQL query calculates the maximuim out-edge for datasets only.
+
+SELECT COUNT(?outgoing) as ?outEdges
+WHERE {
+?source a <http://www.w3.org/ns/dcat#Dataset> .
+?source ?p ?outgoing .
+}
+GROUP BY ?source
+ORDER BY ASC(?outEdges)
+LIMIT 1
\ No newline at end of file
diff --git a/queries/metrics/RM001_out_edges_total.rq b/queries/metrics/RM001_out_edges_total.rq
new file mode 100644
index 0000000..8669f72
--- /dev/null
+++ b/queries/metrics/RM001_out_edges_total.rq
@@ -0,0 +1,7 @@
+# Returns a single number representing the cleaned count of unique outging nodes in the graph.
+
+SELECT COUNT(DISTINCT ?source) as ?uniqueEdges
+WHERE {
+  ?source a <http://www.w3.org/ns/dcat#Dataset> .
+  ?source ?p ?outgoing .
+}
\ No newline at end of file
-- 
GitLab


From e9fd2fca832eb55a8c2139c33f0c24f7425d6726 Mon Sep 17 00:00:00 2001
From: Ralf Klammer <ralf.klammer@tu-dresden.de>
Date: Fri, 14 Mar 2025 10:59:48 +0100
Subject: [PATCH 09/59] [metrics] Add metrics for all resource type by using
 template

---
 docs/macros/main.py                           | 14 +++
 .../resource specific metrics/06_dataset.md   | 93 +------------------
 .../07_publication.md                         |  4 +
 .../08_learning_resource.md                   |  4 +
 .../09_repository.md                          |  4 +
 .../10_article_lhb.md                         |  4 +
 .../resource specific metrics/11_standards.md |  4 +
 .../12_organization.md                        |  4 +
 .../resource specific metrics/13_software.md  |  4 +
 .../resource specific metrics/14_service.md   |  4 +
 .../15_dataservice.md                         |  4 +
 .../16_aggregator.md                          |  4 +
 .../resource specific metrics/17_person.md    |  4 +
 .../resource specific metrics/18_registry.md  |  4 +
 docs/templates/basic_metrics.md               | 93 +++++++++++++++++++
 queries/metrics/RM001_instances_template.rq   | 11 +++
 queries/metrics/RM002_assertions_template.rq  |  6 ++
 queries/metrics/RM003_linkage_template.rq     | 30 ++++++
 .../RM004_1_out_edges_total_template.rq       |  7 ++
 .../metrics/RM004_2_out_edges_min_template.rq | 10 ++
 .../RM004_3_out_edges_median_template.rq      | 15 +++
 .../metrics/RM004_4_out_edges_max_template.rq | 10 ++
 .../RM005_1_in_edges_total_template.rq        |  7 ++
 .../metrics/RM005_2_in_edges_min_template.rq  | 10 ++
 .../RM005_3_in_edges_median_template.rq       | 15 +++
 .../metrics/RM005_4_in_edges_max_template.rq  | 10 ++
 .../metrics/RM006_connectivity_template.rq    | 16 ++++
 27 files changed, 305 insertions(+), 90 deletions(-)
 create mode 100644 docs/metrics/resource specific metrics/07_publication.md
 create mode 100644 docs/metrics/resource specific metrics/08_learning_resource.md
 create mode 100644 docs/metrics/resource specific metrics/09_repository.md
 create mode 100644 docs/metrics/resource specific metrics/10_article_lhb.md
 create mode 100644 docs/metrics/resource specific metrics/11_standards.md
 create mode 100644 docs/metrics/resource specific metrics/12_organization.md
 create mode 100644 docs/metrics/resource specific metrics/13_software.md
 create mode 100644 docs/metrics/resource specific metrics/14_service.md
 create mode 100644 docs/metrics/resource specific metrics/15_dataservice.md
 create mode 100644 docs/metrics/resource specific metrics/16_aggregator.md
 create mode 100644 docs/metrics/resource specific metrics/17_person.md
 create mode 100644 docs/metrics/resource specific metrics/18_registry.md
 create mode 100644 docs/templates/basic_metrics.md
 create mode 100644 queries/metrics/RM001_instances_template.rq
 create mode 100644 queries/metrics/RM002_assertions_template.rq
 create mode 100644 queries/metrics/RM003_linkage_template.rq
 create mode 100644 queries/metrics/RM004_1_out_edges_total_template.rq
 create mode 100644 queries/metrics/RM004_2_out_edges_min_template.rq
 create mode 100644 queries/metrics/RM004_3_out_edges_median_template.rq
 create mode 100644 queries/metrics/RM004_4_out_edges_max_template.rq
 create mode 100644 queries/metrics/RM005_1_in_edges_total_template.rq
 create mode 100644 queries/metrics/RM005_2_in_edges_min_template.rq
 create mode 100644 queries/metrics/RM005_3_in_edges_median_template.rq
 create mode 100644 queries/metrics/RM005_4_in_edges_max_template.rq
 create mode 100644 queries/metrics/RM006_connectivity_template.rq

diff --git a/docs/macros/main.py b/docs/macros/main.py
index 4287ee7..9b2e345 100644
--- a/docs/macros/main.py
+++ b/docs/macros/main.py
@@ -13,3 +13,17 @@ def define_env(env):
             return f"*Keine Ergebnisse verfügbar. Die Datei `{filename}` wurde nicht gefunden.*"
         except IndexError:
             return f"*Fehler: Die angegebene Zeile {start_line} existiert nicht in `{filename}`.*"
+
+    @env.macro
+    def include_template(template_path, resource_type, **kwargs):
+        """
+        Includes a template file and replaces {resource_type} with the given value
+
+        Args:
+            template_path (str): Path to the template file
+            resource_type (str): The resource type URI to inject
+        """
+        content = include_if_exists(template_path, **kwargs)
+        if content:
+            return content.replace("{resource_type}", resource_type)
+        return ""
diff --git a/docs/metrics/resource specific metrics/06_dataset.md b/docs/metrics/resource specific metrics/06_dataset.md
index cab4f21..fbbb7c0 100644
--- a/docs/metrics/resource specific metrics/06_dataset.md	
+++ b/docs/metrics/resource specific metrics/06_dataset.md	
@@ -1,91 +1,4 @@
-# Dataset Resource Metrics
+{% from "templates/basic_metrics.md" import basic_metrics with context %}
+# Dataset
 
-This document analyzes specific metrics for resources of type `Dataset` in the knowledge graph.
-
-## Basic Metrics
-
-### Number of Datasets
-
-see also: [general metrics/instances](/metrics/general%20metrics/01_instances/)
-
-```sparql
-{{ include_if_exists("queries/metrics/RM001_instances.rq") }}
-```
-
-### Connectivity to other resources
-
-```sparql
-{{ include_if_exists("queries/metrics/RM001_connectivity.rq") }}
-```
-
-### Number of Assertions
-
-see also: [general metrics/assortions](/metrics/general%20metrics/02_assertions/)
-
-```sparql
-{{ include_if_exists("queries/metrics/RM001_assertions.rq") }}
-```
-
-### Average linkage
-
-see also: [general metrics/linkage](/metrics/general%20metrics/03_linkage_degree/)
-
-```sparql
-{{ include_if_exists("queries/metrics/RM001_linkage.rq") }}
-```
-
-### Outgoing Edges Statistics
-
-see also: [general metrics/outgoing edges](/metrics/general%20metrics/04_outgoing_edges/)
-
-#### Total outgoing edges
-
-```sparql
-{{ include_if_exists("queries/metrics/RM001_out_edges_total.rq") }}
-```
-
-#### Minimum outgoing edges
-
-```sparql
-{{ include_if_exists("queries/metrics/RM001_out_edges_min.rq") }}
-```
-
-#### Maximum outgoing edges
-
-```sparql
-{{ include_if_exists("queries/metrics/RM001_out_edges_max.rq") }}
-```
-
-#### Median outgoing edges
-
-```sparql
-{{ include_if_exists("queries/metrics/RM001_out_edges_median.rq") }}
-```
-
-### Incoming Edges Statistics
-
-see also: [general metrics/incoming edges](/metrics/general%20metrics/05_incoming_edges/)
-
-#### Total incoming edges
-
-```sparql
-{{ include_if_exists("queries/metrics/RM001_out_edges_total.rq") }}
-```
-
-#### Minimum incoming edges
-
-```sparql
-{{ include_if_exists("queries/metrics/RM001_out_edges_min.rq") }}
-```
-
-#### Maximum incoming edges
-
-```sparql
-{{ include_if_exists("queries/metrics/RM001_out_edges_max.rq") }}
-```
-
-#### Median incoming edges
-
-```sparql
-{{ include_if_exists("queries/metrics/RM001_out_edges_median.rq") }}
-```
+{{ basic_metrics("<http://www.w3.org/ns/dcat#Dataset>") }}
diff --git a/docs/metrics/resource specific metrics/07_publication.md b/docs/metrics/resource specific metrics/07_publication.md
new file mode 100644
index 0000000..46a7d3d
--- /dev/null
+++ b/docs/metrics/resource specific metrics/07_publication.md	
@@ -0,0 +1,4 @@
+{% from "templates/basic_metrics.md" import basic_metrics with context %}
+# Publication
+
+{{ basic_metrics("<http://nfdi4earth.de/ontology/Publication>") }}
diff --git a/docs/metrics/resource specific metrics/08_learning_resource.md b/docs/metrics/resource specific metrics/08_learning_resource.md
new file mode 100644
index 0000000..e9d05b5
--- /dev/null
+++ b/docs/metrics/resource specific metrics/08_learning_resource.md	
@@ -0,0 +1,4 @@
+{% from "templates/basic_metrics.md" import basic_metrics with context %}
+# Learning Resource
+
+{{ basic_metrics("<http://schema.org/LearningResource>") }}
diff --git a/docs/metrics/resource specific metrics/09_repository.md b/docs/metrics/resource specific metrics/09_repository.md
new file mode 100644
index 0000000..a1eb845
--- /dev/null
+++ b/docs/metrics/resource specific metrics/09_repository.md	
@@ -0,0 +1,4 @@
+{% from "templates/basic_metrics.md" import basic_metrics with context %}
+# Repository
+
+{{ basic_metrics("<http://nfdi4earth.de/ontology/Repository>") }}
diff --git a/docs/metrics/resource specific metrics/10_article_lhb.md b/docs/metrics/resource specific metrics/10_article_lhb.md
new file mode 100644
index 0000000..e37dca7
--- /dev/null
+++ b/docs/metrics/resource specific metrics/10_article_lhb.md	
@@ -0,0 +1,4 @@
+{% from "templates/basic_metrics.md" import basic_metrics with context %}
+# Living Handbook Article
+
+{{ basic_metrics("<http://nfdi4earth.de/ontology/LHBArticle>") }}
diff --git a/docs/metrics/resource specific metrics/11_standards.md b/docs/metrics/resource specific metrics/11_standards.md
new file mode 100644
index 0000000..f09b4b5
--- /dev/null
+++ b/docs/metrics/resource specific metrics/11_standards.md	
@@ -0,0 +1,4 @@
+{% from "templates/basic_metrics.md" import basic_metrics with context %}
+# Standards
+
+{{ basic_metrics("<http://nfdi4earth.de/ontology/MetadataStandard>") }}
diff --git a/docs/metrics/resource specific metrics/12_organization.md b/docs/metrics/resource specific metrics/12_organization.md
new file mode 100644
index 0000000..61e6323
--- /dev/null
+++ b/docs/metrics/resource specific metrics/12_organization.md	
@@ -0,0 +1,4 @@
+{% from "templates/basic_metrics.md" import basic_metrics with context %}
+# Organization
+
+{{ basic_metrics("<http://xmlns.com/foaf/0.1/Organization>") }}
diff --git a/docs/metrics/resource specific metrics/13_software.md b/docs/metrics/resource specific metrics/13_software.md
new file mode 100644
index 0000000..bb23391
--- /dev/null
+++ b/docs/metrics/resource specific metrics/13_software.md	
@@ -0,0 +1,4 @@
+{% from "templates/basic_metrics.md" import basic_metrics with context %}
+# Tools & Software
+
+{{ basic_metrics("<http://schema.org/SoftwareSourceCode>") }}
diff --git a/docs/metrics/resource specific metrics/14_service.md b/docs/metrics/resource specific metrics/14_service.md
new file mode 100644
index 0000000..48efc3e
--- /dev/null
+++ b/docs/metrics/resource specific metrics/14_service.md	
@@ -0,0 +1,4 @@
+{% from "templates/basic_metrics.md" import basic_metrics with context %}
+# Service
+
+{{ basic_metrics("<http://www.w3.org/ns/sparql-service-description#Service>") }}
diff --git a/docs/metrics/resource specific metrics/15_dataservice.md b/docs/metrics/resource specific metrics/15_dataservice.md
new file mode 100644
index 0000000..82c9a69
--- /dev/null
+++ b/docs/metrics/resource specific metrics/15_dataservice.md	
@@ -0,0 +1,4 @@
+{% from "templates/basic_metrics.md" import basic_metrics with context %}
+# Data Service
+
+{{ basic_metrics("<http://www.w3.org/ns/dcat#DataService>") }}
diff --git a/docs/metrics/resource specific metrics/16_aggregator.md b/docs/metrics/resource specific metrics/16_aggregator.md
new file mode 100644
index 0000000..114bcfa
--- /dev/null
+++ b/docs/metrics/resource specific metrics/16_aggregator.md	
@@ -0,0 +1,4 @@
+{% from "templates/basic_metrics.md" import basic_metrics with context %}
+# Aggregator
+
+{{ basic_metrics("<http://nfdi4earth.de/ontology/Aggregator>") }}
diff --git a/docs/metrics/resource specific metrics/17_person.md b/docs/metrics/resource specific metrics/17_person.md
new file mode 100644
index 0000000..d806fab
--- /dev/null
+++ b/docs/metrics/resource specific metrics/17_person.md	
@@ -0,0 +1,4 @@
+{% from "templates/basic_metrics.md" import basic_metrics with context %}
+# Person
+
+{{ basic_metrics("<http://schema.org/Person>") }}
diff --git a/docs/metrics/resource specific metrics/18_registry.md b/docs/metrics/resource specific metrics/18_registry.md
new file mode 100644
index 0000000..bfb1803
--- /dev/null
+++ b/docs/metrics/resource specific metrics/18_registry.md	
@@ -0,0 +1,4 @@
+{% from "templates/basic_metrics.md" import basic_metrics with context %}
+# Registry
+
+{{ basic_metrics("<http://nfdi4earth.de/ontology/Registry>") }}
diff --git a/docs/templates/basic_metrics.md b/docs/templates/basic_metrics.md
new file mode 100644
index 0000000..03bef10
--- /dev/null
+++ b/docs/templates/basic_metrics.md
@@ -0,0 +1,93 @@
+{% macro basic_metrics(resource_type) %}
+
+Resource type: {{resource_type}}
+
+## Basic Metrics
+
+### Number of Datasets
+
+see also: [general metrics/instances](/metrics/general%20metrics/01_instances/)
+
+```sparql
+{{ include_template("queries/metrics/RM001_instances_template.rq", resource_type) }}
+```
+
+### Connectivity to other resources
+
+```sparql
+{{ include_template("queries/metrics/RM006_connectivity_template.rq", resource_type) }}
+```
+
+### Number of Assertions
+
+see also: [general metrics/assortions](/metrics/general%20metrics/02_assertions/)
+
+```sparql
+{{ include_template("queries/metrics/RM002_assertions_template.rq", resource_type) }}
+```
+
+### Average linkage
+
+see also: [general metrics/linkage](/metrics/general%20metrics/03_linkage_degree/)
+
+```sparql
+{{ include_template("queries/metrics/RM003_linkage_template.rq", resource_type) }}
+```
+
+### Outgoing Edges Statistics
+
+see also: [general metrics/outgoing edges](/metrics/general%20metrics/04_outgoing_edges/)
+
+#### Total outgoing edges
+
+```sparql
+{{ include_template("queries/metrics/RM004_1_out_edges_total_template.rq", resource_type) }}
+```
+
+#### Minimum outgoing edges
+
+```sparql
+{{ include_template("queries/metrics/RM004_2_out_edges_min_template.rq", resource_type) }}
+```
+
+#### Median outgoing edges
+
+```sparql
+{{ include_template("queries/metrics/RM004_3_out_edges_median_template.rq", resource_type) }}
+```
+
+#### Maximum outgoing edges
+
+```sparql
+{{ include_template("queries/metrics/RM004_4_out_edges_max_template.rq", resource_type) }}
+```
+
+### Incoming Edges Statistics
+
+see also: [general metrics/incoming edges](/metrics/general%20metrics/05_incoming_edges/)
+
+#### Total incoming edges
+
+```sparql
+{{ include_template("queries/metrics/RM005_1_in_edges_total_template.rq", resource_type) }}
+```
+
+#### Minimum incoming edges
+
+```sparql
+{{ include_template("queries/metrics/RM005_2_in_edges_min_template.rq", resource_type) }}
+```
+
+#### Median incoming edges
+
+```sparql
+{{ include_template("queries/metrics/RM005_3_in_edges_median_template.rq", resource_type) }}
+```
+
+#### Maximum incoming edges
+
+```sparql
+{{ include_template("queries/metrics/RM005_4_in_edges_max_template.rq", resource_type) }}
+```
+
+{% endmacro %}
diff --git a/queries/metrics/RM001_instances_template.rq b/queries/metrics/RM001_instances_template.rq
new file mode 100644
index 0000000..f294233
--- /dev/null
+++ b/queries/metrics/RM001_instances_template.rq
@@ -0,0 +1,11 @@
+# Count all instances of type Dataset
+#
+# Explanation:
+# ?dataset a dcat:Dataset finds all resources that are of type Dataset
+# COUNT(DISTINCT ?dataset) counts unique dataset instances
+# We use DISTINCT to avoid counting duplicates if a dataset has multiple types
+
+SELECT (COUNT(DISTINCT ?dataset) AS ?datasetCount)
+WHERE {
+  ?dataset a {resource_type} .
+}
diff --git a/queries/metrics/RM002_assertions_template.rq b/queries/metrics/RM002_assertions_template.rq
new file mode 100644
index 0000000..404266a
--- /dev/null
+++ b/queries/metrics/RM002_assertions_template.rq
@@ -0,0 +1,6 @@
+# Total number of assertions (for resource type: Dataset)
+
+SELECT (COUNT(*) AS ?numAssertions)
+WHERE {
+  ?subject ?predicate {resource_type} .
+}
diff --git a/queries/metrics/RM003_linkage_template.rq b/queries/metrics/RM003_linkage_template.rq
new file mode 100644
index 0000000..6cf796d
--- /dev/null
+++ b/queries/metrics/RM003_linkage_template.rq
@@ -0,0 +1,30 @@
+# The average linkage degree for datasets only
+# (i.e.: how many assertions per dataset does the graph contain)
+
+SELECT (AVG(?totalDegree) as ?avgLinkageDegree)
+WHERE {
+  {
+    SELECT ?entity ((?outDegree + ?inDegree) as ?totalDegree)
+    WHERE {
+      # Only consider entities that are datasets
+      ?entity a {resource_type} .
+
+      {
+        SELECT ?entity (COUNT(*) as ?outDegree)
+        WHERE {
+          ?entity a {resource_type} .
+          ?entity ?p ?o .
+        }
+        GROUP BY ?entity
+      }
+      {
+        SELECT ?entity (COUNT(*) as ?inDegree)
+        WHERE {
+          ?entity a {resource_type} .
+          ?s ?p ?entity .  # Hier zählen wir die Verbindungen, die auf das Dataset zeigen
+        }
+        GROUP BY ?entity
+      }
+    }
+  }
+}
diff --git a/queries/metrics/RM004_1_out_edges_total_template.rq b/queries/metrics/RM004_1_out_edges_total_template.rq
new file mode 100644
index 0000000..5788a6d
--- /dev/null
+++ b/queries/metrics/RM004_1_out_edges_total_template.rq
@@ -0,0 +1,7 @@
+# Returns a single number representing the cleaned count of unique outging nodes in the graph.
+
+SELECT COUNT(DISTINCT ?source) as ?uniqueEdges
+WHERE {
+  ?source a {resource_type} .
+  ?source ?p ?outgoing .
+}
\ No newline at end of file
diff --git a/queries/metrics/RM004_2_out_edges_min_template.rq b/queries/metrics/RM004_2_out_edges_min_template.rq
new file mode 100644
index 0000000..817ba5e
--- /dev/null
+++ b/queries/metrics/RM004_2_out_edges_min_template.rq
@@ -0,0 +1,10 @@
+# This SPARQL query calculates the maximuim out-edge for datasets only.
+
+SELECT COUNT(?outgoing) as ?outEdges
+WHERE {
+?source a {resource_type} .
+?source ?p ?outgoing .
+}
+GROUP BY ?source
+ORDER BY ASC(?outEdges)
+LIMIT 1
\ No newline at end of file
diff --git a/queries/metrics/RM004_3_out_edges_median_template.rq b/queries/metrics/RM004_3_out_edges_median_template.rq
new file mode 100644
index 0000000..1bbc201
--- /dev/null
+++ b/queries/metrics/RM004_3_out_edges_median_template.rq
@@ -0,0 +1,15 @@
+# This SPARQL query calculates the median out-edge for datasets only.
+# The placeholder {median_position} must be replaced with the actual position of the median.
+
+SELECT ?outEdges as ?outMedian
+WHERE {
+  SELECT ?source (COUNT(?outgoing) as ?outEdges)
+  WHERE {
+    ?source a {resource_type} .
+    ?source ?p ?outgoing .
+  }
+  GROUP BY ?source
+  ORDER BY ASC(?outEdges)
+}
+OFFSET {median_position}
+LIMIT 1
\ No newline at end of file
diff --git a/queries/metrics/RM004_4_out_edges_max_template.rq b/queries/metrics/RM004_4_out_edges_max_template.rq
new file mode 100644
index 0000000..02bbb4f
--- /dev/null
+++ b/queries/metrics/RM004_4_out_edges_max_template.rq
@@ -0,0 +1,10 @@
+# This SPARQL query calculates the maximum out-edge for datasets only.
+
+SELECT COUNT(?outgoing) as ?outEdges
+WHERE {
+?source a {resource_type} .
+?source ?p ?outgoing .
+}
+GROUP BY ?source
+ORDER BY DESC(?outEdges)
+LIMIT 1
\ No newline at end of file
diff --git a/queries/metrics/RM005_1_in_edges_total_template.rq b/queries/metrics/RM005_1_in_edges_total_template.rq
new file mode 100644
index 0000000..9d3662a
--- /dev/null
+++ b/queries/metrics/RM005_1_in_edges_total_template.rq
@@ -0,0 +1,7 @@
+# Returns a single number representing the cleaned count of unique incoming nodes in the graph.
+
+SELECT (COUNT(DISTINCT ?target) as ?uniqueEdges)
+WHERE {
+  ?incoming ?p ?target .
+  ?target a {resource_type} .
+}
\ No newline at end of file
diff --git a/queries/metrics/RM005_2_in_edges_min_template.rq b/queries/metrics/RM005_2_in_edges_min_template.rq
new file mode 100644
index 0000000..e44cb69
--- /dev/null
+++ b/queries/metrics/RM005_2_in_edges_min_template.rq
@@ -0,0 +1,10 @@
+# This SPARQL query calculates the minimum out-edge for datasets only.
+
+SELECT ?target (COUNT(?incoming) as ?inEdges)
+WHERE {
+?target a {resource_type} .
+?incoming ?p ?target .
+}
+GROUP BY ?target
+ORDER BY ASC(?inEdges)
+LIMIT 1
diff --git a/queries/metrics/RM005_3_in_edges_median_template.rq b/queries/metrics/RM005_3_in_edges_median_template.rq
new file mode 100644
index 0000000..f0d7ce9
--- /dev/null
+++ b/queries/metrics/RM005_3_in_edges_median_template.rq
@@ -0,0 +1,15 @@
+# This SPARQL query calculates the median in-edge for datasets only.
+# The placeholder {median_position} must be replaced with the actual position of the median.
+
+SELECT ?inEdges as ?inMedian
+WHERE {
+    SELECT ?target (COUNT(?incoming) as ?inEdges)
+    WHERE {
+    ?target a {resource_type} .
+    ?incoming ?p ?target .
+    }
+    GROUP BY ?target
+    ORDER BY ASC(?inEdges)
+}
+OFFSET {median_position}
+LIMIT 1
diff --git a/queries/metrics/RM005_4_in_edges_max_template.rq b/queries/metrics/RM005_4_in_edges_max_template.rq
new file mode 100644
index 0000000..831b3cb
--- /dev/null
+++ b/queries/metrics/RM005_4_in_edges_max_template.rq
@@ -0,0 +1,10 @@
+# This SPARQL query calculates the maximum out-edge for datasets only.
+
+SELECT ?target (COUNT(?incoming) as ?inEdges)
+WHERE {
+?target a {resource_type} .
+?incoming ?p ?target .
+}
+GROUP BY ?target
+ORDER BY DESC(?inEdges)
+LIMIT 1
diff --git a/queries/metrics/RM006_connectivity_template.rq b/queries/metrics/RM006_connectivity_template.rq
new file mode 100644
index 0000000..110887c
--- /dev/null
+++ b/queries/metrics/RM006_connectivity_template.rq
@@ -0,0 +1,16 @@
+# Analysis of total connected resources for datasets
+#
+# Explanation:
+# Counts the number of resources that are connected to datasets
+# This gives us a metric for dataset connectivity in the graph
+
+SELECT (COUNT(DISTINCT ?connected) as ?numConnectedResources)
+WHERE {
+  ?dataset a {resource_type} ;
+          ?property ?connected .
+
+  # Ensure connected resource is not a literal
+  FILTER(isIRI(?connected))
+  # Exclude self-references to datasets
+  FILTER(?connected != ?dataset)
+}
-- 
GitLab


From 22bf16c39a1bec7d83703a4080312dfafbb594ef Mon Sep 17 00:00:00 2001
From: Ralf Klammer <ralf.klammer@tu-dresden.de>
Date: Fri, 14 Mar 2025 11:01:46 +0100
Subject: [PATCH 10/59] [metrics] Remove dataset-queries

---
 queries/metrics/RM001_assertions.rq       |  8 ------
 queries/metrics/RM001_connectivity.rq     | 19 --------------
 queries/metrics/RM001_in_edges_max.rq     | 10 --------
 queries/metrics/RM001_in_edges_median.rq  | 15 ------------
 queries/metrics/RM001_in_edges_min.rq     | 10 --------
 queries/metrics/RM001_in_edges_total.rq   |  7 ------
 queries/metrics/RM001_instances.rq        | 13 ----------
 queries/metrics/RM001_linkage.rq          | 30 -----------------------
 queries/metrics/RM001_out_edges_max.rq    | 10 --------
 queries/metrics/RM001_out_edges_median.rq | 15 ------------
 queries/metrics/RM001_out_edges_min.rq    | 10 --------
 queries/metrics/RM001_out_edges_total.rq  |  7 ------
 12 files changed, 154 deletions(-)
 delete mode 100644 queries/metrics/RM001_assertions.rq
 delete mode 100644 queries/metrics/RM001_connectivity.rq
 delete mode 100644 queries/metrics/RM001_in_edges_max.rq
 delete mode 100644 queries/metrics/RM001_in_edges_median.rq
 delete mode 100644 queries/metrics/RM001_in_edges_min.rq
 delete mode 100644 queries/metrics/RM001_in_edges_total.rq
 delete mode 100644 queries/metrics/RM001_instances.rq
 delete mode 100644 queries/metrics/RM001_linkage.rq
 delete mode 100644 queries/metrics/RM001_out_edges_max.rq
 delete mode 100644 queries/metrics/RM001_out_edges_median.rq
 delete mode 100644 queries/metrics/RM001_out_edges_min.rq
 delete mode 100644 queries/metrics/RM001_out_edges_total.rq

diff --git a/queries/metrics/RM001_assertions.rq b/queries/metrics/RM001_assertions.rq
deleted file mode 100644
index 051632a..0000000
--- a/queries/metrics/RM001_assertions.rq
+++ /dev/null
@@ -1,8 +0,0 @@
-# Total number of assertions (for resource type: Dataset)
-
-PREFIX dcat: <http://www.w3.org/ns/dcat#>
-
-SELECT (COUNT(*) AS ?numAssertions)
-WHERE {
-  ?subject ?predicate dcat:Dataset .
-}
diff --git a/queries/metrics/RM001_connectivity.rq b/queries/metrics/RM001_connectivity.rq
deleted file mode 100644
index 1998eba..0000000
--- a/queries/metrics/RM001_connectivity.rq
+++ /dev/null
@@ -1,19 +0,0 @@
-# Analysis of total connected resources for datasets
-#
-# Explanation:
-# Counts the number of resources that are connected to datasets
-# This gives us a metric for dataset connectivity in the graph
-
-PREFIX dcat: <http://www.w3.org/ns/dcat#>
-PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>
-
-SELECT (COUNT(DISTINCT ?connected) as ?numConnectedResources)
-WHERE {
-  ?dataset a dcat:Dataset ;
-          ?property ?connected .
-
-  # Ensure connected resource is not a literal
-  FILTER(isIRI(?connected))
-  # Exclude self-references to datasets
-  FILTER(?connected != ?dataset)
-}
diff --git a/queries/metrics/RM001_in_edges_max.rq b/queries/metrics/RM001_in_edges_max.rq
deleted file mode 100644
index 1d9a825..0000000
--- a/queries/metrics/RM001_in_edges_max.rq
+++ /dev/null
@@ -1,10 +0,0 @@
-# This SPARQL query calculates the maximum out-edge for datasets only.
-
-SELECT ?target (COUNT(?incoming) as ?inEdges)
-WHERE {
-?target a <http://www.w3.org/ns/dcat#Dataset> .
-?incoming ?p ?target .
-}
-GROUP BY ?target
-ORDER BY DESC(?inEdges)
-LIMIT 1
diff --git a/queries/metrics/RM001_in_edges_median.rq b/queries/metrics/RM001_in_edges_median.rq
deleted file mode 100644
index d2ea70f..0000000
--- a/queries/metrics/RM001_in_edges_median.rq
+++ /dev/null
@@ -1,15 +0,0 @@
-# This SPARQL query calculates the median in-edge for datasets only.
-# The placeholder {median_position} must be replaced with the actual position of the median.
-
-SELECT ?inEdges as ?inMedian
-WHERE {
-    SELECT ?target (COUNT(?incoming) as ?inEdges)
-    WHERE {
-    ?target a <http://www.w3.org/ns/dcat#Dataset> .
-    ?incoming ?p ?target .
-    }
-    GROUP BY ?target
-    ORDER BY ASC(?inEdges)
-}
-OFFSET {median_position}
-LIMIT 1
diff --git a/queries/metrics/RM001_in_edges_min.rq b/queries/metrics/RM001_in_edges_min.rq
deleted file mode 100644
index f39e6cb..0000000
--- a/queries/metrics/RM001_in_edges_min.rq
+++ /dev/null
@@ -1,10 +0,0 @@
-# This SPARQL query calculates the minimum out-edge for datasets only.
-
-SELECT ?target (COUNT(?incoming) as ?inEdges)
-WHERE {
-?target a <http://www.w3.org/ns/dcat#Dataset> .
-?incoming ?p ?target .
-}
-GROUP BY ?target
-ORDER BY ASC(?inEdges)
-LIMIT 1
diff --git a/queries/metrics/RM001_in_edges_total.rq b/queries/metrics/RM001_in_edges_total.rq
deleted file mode 100644
index 27b3569..0000000
--- a/queries/metrics/RM001_in_edges_total.rq
+++ /dev/null
@@ -1,7 +0,0 @@
-# Returns a single number representing the cleaned count of unique incoming nodes in the graph.
-
-SELECT (COUNT(DISTINCT ?target) as ?uniqueEdges)
-WHERE {
-  ?incoming ?p ?target .
-  ?target a <http://www.w3.org/ns/dcat#Dataset> .
-}
\ No newline at end of file
diff --git a/queries/metrics/RM001_instances.rq b/queries/metrics/RM001_instances.rq
deleted file mode 100644
index 73a7b0c..0000000
--- a/queries/metrics/RM001_instances.rq
+++ /dev/null
@@ -1,13 +0,0 @@
-# Count all instances of type Dataset
-#
-# Explanation:
-# ?dataset a dcat:Dataset finds all resources that are of type Dataset
-# COUNT(DISTINCT ?dataset) counts unique dataset instances
-# We use DISTINCT to avoid counting duplicates if a dataset has multiple types
-
-PREFIX dcat: <http://www.w3.org/ns/dcat#>
-
-SELECT (COUNT(DISTINCT ?dataset) AS ?datasetCount)
-WHERE {
-  ?dataset a dcat:Dataset .
-}
diff --git a/queries/metrics/RM001_linkage.rq b/queries/metrics/RM001_linkage.rq
deleted file mode 100644
index f379777..0000000
--- a/queries/metrics/RM001_linkage.rq
+++ /dev/null
@@ -1,30 +0,0 @@
-# The average linkage degree for datasets only
-# (i.e.: how many assertions per dataset does the graph contain)
-
-SELECT (AVG(?totalDegree) as ?avgLinkageDegree)
-WHERE {
-  {
-    SELECT ?entity ((?outDegree + ?inDegree) as ?totalDegree)
-    WHERE {
-      # Only consider entities that are datasets
-      ?entity a <http://www.w3.org/ns/dcat#Dataset> .
-
-      {
-        SELECT ?entity (COUNT(*) as ?outDegree)
-        WHERE {
-          ?entity a <http://www.w3.org/ns/dcat#Dataset> .
-          ?entity ?p ?o .
-        }
-        GROUP BY ?entity
-      }
-      {
-        SELECT ?entity (COUNT(*) as ?inDegree)
-        WHERE {
-          ?entity a <http://www.w3.org/ns/dcat#Dataset> .
-          ?s ?p ?entity .  # Hier zählen wir die Verbindungen, die auf das Dataset zeigen
-        }
-        GROUP BY ?entity
-      }
-    }
-  }
-}
diff --git a/queries/metrics/RM001_out_edges_max.rq b/queries/metrics/RM001_out_edges_max.rq
deleted file mode 100644
index 4503611..0000000
--- a/queries/metrics/RM001_out_edges_max.rq
+++ /dev/null
@@ -1,10 +0,0 @@
-# This SPARQL query calculates the maximum out-edge for datasets only.
-
-SELECT COUNT(?outgoing) as ?outEdges
-WHERE {
-?source a <http://www.w3.org/ns/dcat#Dataset> .
-?source ?p ?outgoing .
-}
-GROUP BY ?source
-ORDER BY DESC(?outEdges)
-LIMIT 1
\ No newline at end of file
diff --git a/queries/metrics/RM001_out_edges_median.rq b/queries/metrics/RM001_out_edges_median.rq
deleted file mode 100644
index dd705cb..0000000
--- a/queries/metrics/RM001_out_edges_median.rq
+++ /dev/null
@@ -1,15 +0,0 @@
-# This SPARQL query calculates the median out-edge for datasets only.
-# The placeholder {median_position} must be replaced with the actual position of the median.
-
-SELECT ?outEdges as ?outMedian
-WHERE {
-  SELECT ?source (COUNT(?outgoing) as ?outEdges)
-  WHERE {
-    ?source a <http://www.w3.org/ns/dcat#Dataset> .
-    ?source ?p ?outgoing .
-  }
-  GROUP BY ?source
-  ORDER BY ASC(?outEdges)
-}
-OFFSET {median_position}
-LIMIT 1
\ No newline at end of file
diff --git a/queries/metrics/RM001_out_edges_min.rq b/queries/metrics/RM001_out_edges_min.rq
deleted file mode 100644
index 55bcfcf..0000000
--- a/queries/metrics/RM001_out_edges_min.rq
+++ /dev/null
@@ -1,10 +0,0 @@
-# This SPARQL query calculates the maximuim out-edge for datasets only.
-
-SELECT COUNT(?outgoing) as ?outEdges
-WHERE {
-?source a <http://www.w3.org/ns/dcat#Dataset> .
-?source ?p ?outgoing .
-}
-GROUP BY ?source
-ORDER BY ASC(?outEdges)
-LIMIT 1
\ No newline at end of file
diff --git a/queries/metrics/RM001_out_edges_total.rq b/queries/metrics/RM001_out_edges_total.rq
deleted file mode 100644
index 8669f72..0000000
--- a/queries/metrics/RM001_out_edges_total.rq
+++ /dev/null
@@ -1,7 +0,0 @@
-# Returns a single number representing the cleaned count of unique outging nodes in the graph.
-
-SELECT COUNT(DISTINCT ?source) as ?uniqueEdges
-WHERE {
-  ?source a <http://www.w3.org/ns/dcat#Dataset> .
-  ?source ?p ?outgoing .
-}
\ No newline at end of file
-- 
GitLab


From 2f3dec4a1713ad565f6db810d22f7112b16189db Mon Sep 17 00:00:00 2001
From: Ralf Klammer <ralf.klammer@tu-dresden.de>
Date: Fri, 14 Mar 2025 13:41:55 +0100
Subject: [PATCH 11/59] [metrics][kg_analysis] Automatic metrics on resources

---
 scripts/kg_analysis/metrics_runner.py | 188 +++++++++++++++++++++++++-
 scripts/kg_analysis/query_runner.py   |   1 -
 2 files changed, 185 insertions(+), 4 deletions(-)

diff --git a/scripts/kg_analysis/metrics_runner.py b/scripts/kg_analysis/metrics_runner.py
index 4fd7144..6f59afe 100644
--- a/scripts/kg_analysis/metrics_runner.py
+++ b/scripts/kg_analysis/metrics_runner.py
@@ -6,11 +6,13 @@ import logging
 from time import time
 
 from abc import ABC, abstractmethod
+from copy import deepcopy
 from datetime import datetime
 from pathlib import Path
 
 from typing import Optional
 
+from . import rprint
 from .query_runner import QueryRunner
 
 log = logging.getLogger(__name__)
@@ -55,10 +57,12 @@ class MetricsRunnerBase(ABC):
         except Exception as e:
             log.error(f"Failed to run metric: {e}")
             self.fail = True
-        self._execution_time += time() - start_time
+        self._execution_time = time() - start_time
         if result and result["results"]["bindings"]:
+            if not result["results"]["bindings"][0].values():
+                return 0
             return list(result["results"]["bindings"][0].values())[0]["value"]
-        return None
+        return "-"
 
     @abstractmethod
     def run(self) -> dict:
@@ -85,6 +89,7 @@ class MetricsRunner_001(MetricsRunnerBase):
     """Runner for Metric 001: Instance Count"""
 
     _query_file = "GM001.rq"
+    _output_file = "GM001.txt"
 
     def run(self):
         result = self.query_metric(self.query_path)
@@ -172,6 +177,182 @@ class MetricsRunner_Edges(MetricsRunnerBase):
         self.save(result_text)
 
 
+class MetricsRunner_Resources(MetricsRunnerBase):
+    query_templates = {
+        "instances": {
+            "name": "Number of Resources",
+            "file": "RM001_instances_template.rq",
+            "execute": True,
+        },
+        "assertions": {
+            "name": "Number of Assertions",
+            "file": "RM002_assertions_template.rq",
+            "execute": False,
+        },
+        "linkage": {
+            "name": "Average Linkage",
+            "file": "RM003_linkage_template.rq",
+            "execute": True,
+        },
+        "edges_out": {
+            "name": "Edges - outgoing",
+            "files": {
+                "total": {
+                    "name": "Total number of outgoing edges",
+                    "file": "RM004_1_out_edges_total_template.rq",
+                    "execute": True,
+                },
+                "min": {
+                    "name": "Minimum of outgoing edges",
+                    "file": "RM004_2_out_edges_min_template.rq",
+                    "execute": True,
+                },
+                "median": {
+                    "name": "Median of outgoing edges",
+                    "file": "RM004_3_out_edges_median_template.rq",
+                    "execute": False,
+                },
+                "max": {
+                    "name": "Maximum of outgoing edges",
+                    "file": "RM004_4_out_edges_max_template.rq",
+                    "execute": True,
+                },
+            },
+        },
+        "edges_in": {
+            "name": "Edges - incoming",
+            "files": {
+                "total": {
+                    "name": "Total number of incoming edges",
+                    "file": "RM005_1_in_edges_total_template.rq",
+                    "execute": True,
+                },
+                "min": {
+                    "name": "Minimum of incoming edges",
+                    "file": "RM005_2_in_edges_min_template.rq",
+                    "execute": True,
+                },
+                "median": {
+                    "name": "Median of incoming edges",
+                    "file": "RM005_3_in_edges_median_template.rq",
+                    "execute": False,
+                },
+                "max": {
+                    "name": "Maximum of incoming edges",
+                    "file": "RM005_4_in_edges_max_template.rq",
+                    "execute": True,
+                },
+            },
+        },
+        # "edges_out": {
+        #     "name": "Edges - outgoing",
+        #     "file": "RM004_out_edges_template.rq",
+        # "execute": True,  # },
+        # "edges_in": {
+        #     "name": "Edges - incoming",
+        #     "file": "RM005_in_edges_template.rq",
+        # "execute": True,  # },
+        "connectivity": {
+            "name": "Connectivity",
+            "file": "RM006_connectivity_template.rq",
+            "execute": True,
+        },
+    }
+
+    resource_types = {
+        "dataset": {
+            "file": "06_dataset.md",
+            "uri": "<http://www.w3.org/ns/dcat#Dataset>",
+        },
+        "publication": {
+            "file": "07_publication.md",
+            "uri": "<http://nfdi4earth.de/ontology/Registry>",
+        },
+        "learning_resource": {
+            "file": "08_learning_resource.md",
+            "uri": "<http://schema.org/LearningResource>",
+        },
+        "repository": {
+            "file": "09_repository.md",
+            "uri": "<http://nfdi4earth.de/ontology/Repository>",
+        },
+        "article_lhb": {
+            "file": "10_article_lhb.md",
+            "uri": "<http://nfdi4earth.de/ontology/LHBArticle>",
+        },
+        "standards": {
+            "file": "11_standards.md",
+            "uri": "<http://nfdi4earth.de/ontology/MetadataStandard>",
+        },
+        # "organization": {
+        #     "file": "12_organization.md",
+        #     "uri": "<http://xmlns.com/foaf/0.1/Organization>",
+        # },
+        "software": {
+            "file": "13_software.md",
+            "uri": "<http://schema.org/SoftwareSourceCode>",
+        },
+        "service": {
+            "file": "14_service.md",
+            "uri": "<http://www.w3.org/ns/sparql-service-description#Service>",
+        },
+        "data_service": {
+            "file": "15_data_service.md",
+            "uri": "<http://www.w3.org/ns/dcat#DataService>",
+        },
+        "aggregator": {
+            "file": "16_aggregator.md",
+            "uri": "<http://nfdi4earth.de/ontology/Aggregator>",
+        },
+        "person": {
+            "file": "17_person.md",
+            "uri": "<http://schema.org/Person>",
+        },
+        "registry": {
+            "file": "18_registry.md",
+            "uri": "<http://nfdi4earth.de/ontology/Registry>",
+        },
+    }
+
+    def _get_result(self, query, resource_type):
+        log.info(f"Metric: {query['name']} ({query['file']})")
+        if query["execute"] is False:
+            return
+        result = self.query_metric(
+            self.get_query_path(query["file"]),
+            replace_dict={
+                "{resource_type}": resource_type,
+            },
+        )
+        query["result"] = result
+        query["execution_time"] = round(self._execution_time, 2)
+
+    def run(self):
+        for resource_type, data in self.resource_types.items():
+            log.info(f"Analyzing: {data["uri"]}")
+            queries = deepcopy(self.query_templates)
+            for metric, query in queries.items():
+                # if metric not in ["instances", "edges_out"]:
+                #     continue
+                # print(query)
+                if "files" in query:
+                    print(query["files"])
+                    for sub_query in query["files"].values():
+                        self._get_result(sub_query, data["uri"])
+                else:
+                    self._get_result(query, data["uri"])
+                # result = self.query_metric(
+                #     self.get_query_path(query["file"]),
+                #     replace_dict={
+                #         "{resource_type}": data["uri"],
+                #     },
+                # )
+                # query["result"] = result
+                # query["execution_time"] = round(self._execution_time, 2)
+            self.resource_types[resource_type]["queries"] = queries
+        rprint(self.resource_types)
+
+
 class MetricsRunner(ABC):
 
     def run(self):
@@ -179,4 +360,5 @@ class MetricsRunner(ABC):
         # MetricsRunner_002().run()
         # MetricsRunner_003().run()
         # MetricsRunner_Edges("outgoing").run()
-        MetricsRunner_Edges("incoming").run()
+        # MetricsRunner_Edges("incoming").run()
+        MetricsRunner_Resources().run()
diff --git a/scripts/kg_analysis/query_runner.py b/scripts/kg_analysis/query_runner.py
index 9978138..c6567b7 100644
--- a/scripts/kg_analysis/query_runner.py
+++ b/scripts/kg_analysis/query_runner.py
@@ -35,7 +35,6 @@ class QueryRunner:
         if replace_dict:
             for r_key, r_value in replace_dict.items():
                 query = query.replace(r_key, str(r_value))
-
         # Set the query and execute
         self.sparql.setQuery(query)
         result = self.sparql.query().convert()
-- 
GitLab


From 7e7d198bd1e9956b372b63791517af4a6f4c401d Mon Sep 17 00:00:00 2001
From: Ralf Klammer <ralf.klammer@tu-dresden.de>
Date: Fri, 14 Mar 2025 13:49:03 +0100
Subject: [PATCH 12/59] [metrics] Remove all occurances of term 'dataset' in
 resource templates

---
 docs/templates/basic_metrics.md                      |  2 +-
 queries/metrics/RM001_instances_template.rq          | 12 ++++++------
 queries/metrics/RM002_assertions_template.rq         |  2 +-
 queries/metrics/RM003_linkage_template.rq            |  8 ++++----
 queries/metrics/RM004_2_out_edges_min_template.rq    |  2 +-
 queries/metrics/RM004_3_out_edges_median_template.rq |  2 +-
 queries/metrics/RM004_4_out_edges_max_template.rq    |  2 +-
 queries/metrics/RM005_1_in_edges_total_template.rq   |  2 +-
 queries/metrics/RM005_2_in_edges_min_template.rq     |  2 +-
 queries/metrics/RM005_3_in_edges_median_template.rq  |  2 +-
 queries/metrics/RM005_4_in_edges_max_template.rq     |  2 +-
 queries/metrics/RM006_connectivity_template.rq       | 12 ++++++------
 12 files changed, 25 insertions(+), 25 deletions(-)

diff --git a/docs/templates/basic_metrics.md b/docs/templates/basic_metrics.md
index 03bef10..8184739 100644
--- a/docs/templates/basic_metrics.md
+++ b/docs/templates/basic_metrics.md
@@ -4,7 +4,7 @@ Resource type: {{resource_type}}
 
 ## Basic Metrics
 
-### Number of Datasets
+### Number of Entitis
 
 see also: [general metrics/instances](/metrics/general%20metrics/01_instances/)
 
diff --git a/queries/metrics/RM001_instances_template.rq b/queries/metrics/RM001_instances_template.rq
index f294233..89c4b84 100644
--- a/queries/metrics/RM001_instances_template.rq
+++ b/queries/metrics/RM001_instances_template.rq
@@ -1,11 +1,11 @@
-# Count all instances of type Dataset
+# Count all instances of the given entity
 #
 # Explanation:
-# ?dataset a dcat:Dataset finds all resources that are of type Dataset
-# COUNT(DISTINCT ?dataset) counts unique dataset instances
-# We use DISTINCT to avoid counting duplicates if a dataset has multiple types
+# ?resource a {resource_type} finds all resources that are of type {resource_type}
+# COUNT(DISTINCT ?resource) counts unique resource instances
+# We use DISTINCT to avoid counting duplicates if a resource has multiple types
 
-SELECT (COUNT(DISTINCT ?dataset) AS ?datasetCount)
+SELECT (COUNT(DISTINCT ?resource) AS ?resourceCount)
 WHERE {
-  ?dataset a {resource_type} .
+  ?resource a {resource_type} .
 }
diff --git a/queries/metrics/RM002_assertions_template.rq b/queries/metrics/RM002_assertions_template.rq
index 404266a..33c105c 100644
--- a/queries/metrics/RM002_assertions_template.rq
+++ b/queries/metrics/RM002_assertions_template.rq
@@ -1,4 +1,4 @@
-# Total number of assertions (for resource type: Dataset)
+# Total number of assertions (for resource type: {resource_type})
 
 SELECT (COUNT(*) AS ?numAssertions)
 WHERE {
diff --git a/queries/metrics/RM003_linkage_template.rq b/queries/metrics/RM003_linkage_template.rq
index 6cf796d..08064de 100644
--- a/queries/metrics/RM003_linkage_template.rq
+++ b/queries/metrics/RM003_linkage_template.rq
@@ -1,12 +1,12 @@
-# The average linkage degree for datasets only
-# (i.e.: how many assertions per dataset does the graph contain)
+# The average linkage degree for specific resource type only
+# (i.e.: how many assertions per {resource_type} does the graph contain)
 
 SELECT (AVG(?totalDegree) as ?avgLinkageDegree)
 WHERE {
   {
     SELECT ?entity ((?outDegree + ?inDegree) as ?totalDegree)
     WHERE {
-      # Only consider entities that are datasets
+      # Only consider entities that are of given resource
       ?entity a {resource_type} .
 
       {
@@ -21,7 +21,7 @@ WHERE {
         SELECT ?entity (COUNT(*) as ?inDegree)
         WHERE {
           ?entity a {resource_type} .
-          ?s ?p ?entity .  # Hier zählen wir die Verbindungen, die auf das Dataset zeigen
+          ?s ?p ?entity .  # Here we count the connections pointing to the resource
         }
         GROUP BY ?entity
       }
diff --git a/queries/metrics/RM004_2_out_edges_min_template.rq b/queries/metrics/RM004_2_out_edges_min_template.rq
index 817ba5e..3fb1bff 100644
--- a/queries/metrics/RM004_2_out_edges_min_template.rq
+++ b/queries/metrics/RM004_2_out_edges_min_template.rq
@@ -1,4 +1,4 @@
-# This SPARQL query calculates the maximuim out-edge for datasets only.
+# This SPARQL query calculates the maximuim out-edge for {resource_type} only.
 
 SELECT COUNT(?outgoing) as ?outEdges
 WHERE {
diff --git a/queries/metrics/RM004_3_out_edges_median_template.rq b/queries/metrics/RM004_3_out_edges_median_template.rq
index 1bbc201..7f0c784 100644
--- a/queries/metrics/RM004_3_out_edges_median_template.rq
+++ b/queries/metrics/RM004_3_out_edges_median_template.rq
@@ -1,4 +1,4 @@
-# This SPARQL query calculates the median out-edge for datasets only.
+# This SPARQL query calculates the median out-edge for {resource_type} only.
 # The placeholder {median_position} must be replaced with the actual position of the median.
 
 SELECT ?outEdges as ?outMedian
diff --git a/queries/metrics/RM004_4_out_edges_max_template.rq b/queries/metrics/RM004_4_out_edges_max_template.rq
index 02bbb4f..3bf4739 100644
--- a/queries/metrics/RM004_4_out_edges_max_template.rq
+++ b/queries/metrics/RM004_4_out_edges_max_template.rq
@@ -1,4 +1,4 @@
-# This SPARQL query calculates the maximum out-edge for datasets only.
+# This SPARQL query calculates the maximum out-edge for {resource_type} only.
 
 SELECT COUNT(?outgoing) as ?outEdges
 WHERE {
diff --git a/queries/metrics/RM005_1_in_edges_total_template.rq b/queries/metrics/RM005_1_in_edges_total_template.rq
index 9d3662a..285ce77 100644
--- a/queries/metrics/RM005_1_in_edges_total_template.rq
+++ b/queries/metrics/RM005_1_in_edges_total_template.rq
@@ -1,4 +1,4 @@
-# Returns a single number representing the cleaned count of unique incoming nodes in the graph.
+# Returns a single number representing the cleaned count of unique incoming nodes for {resource_type} only.
 
 SELECT (COUNT(DISTINCT ?target) as ?uniqueEdges)
 WHERE {
diff --git a/queries/metrics/RM005_2_in_edges_min_template.rq b/queries/metrics/RM005_2_in_edges_min_template.rq
index e44cb69..c03d47a 100644
--- a/queries/metrics/RM005_2_in_edges_min_template.rq
+++ b/queries/metrics/RM005_2_in_edges_min_template.rq
@@ -1,4 +1,4 @@
-# This SPARQL query calculates the minimum out-edge for datasets only.
+# This SPARQL query calculates the minimum out-edge for {resource_type} only.
 
 SELECT ?target (COUNT(?incoming) as ?inEdges)
 WHERE {
diff --git a/queries/metrics/RM005_3_in_edges_median_template.rq b/queries/metrics/RM005_3_in_edges_median_template.rq
index f0d7ce9..fa2b9c9 100644
--- a/queries/metrics/RM005_3_in_edges_median_template.rq
+++ b/queries/metrics/RM005_3_in_edges_median_template.rq
@@ -1,4 +1,4 @@
-# This SPARQL query calculates the median in-edge for datasets only.
+# This SPARQL query calculates the median in-edge for {resource_type} only.
 # The placeholder {median_position} must be replaced with the actual position of the median.
 
 SELECT ?inEdges as ?inMedian
diff --git a/queries/metrics/RM005_4_in_edges_max_template.rq b/queries/metrics/RM005_4_in_edges_max_template.rq
index 831b3cb..69dc4d6 100644
--- a/queries/metrics/RM005_4_in_edges_max_template.rq
+++ b/queries/metrics/RM005_4_in_edges_max_template.rq
@@ -1,4 +1,4 @@
-# This SPARQL query calculates the maximum out-edge for datasets only.
+# This SPARQL query calculates the maximum out-edge for {resource_type} only.
 
 SELECT ?target (COUNT(?incoming) as ?inEdges)
 WHERE {
diff --git a/queries/metrics/RM006_connectivity_template.rq b/queries/metrics/RM006_connectivity_template.rq
index 110887c..113022d 100644
--- a/queries/metrics/RM006_connectivity_template.rq
+++ b/queries/metrics/RM006_connectivity_template.rq
@@ -1,16 +1,16 @@
-# Analysis of total connected resources for datasets
+# Analysis of total connected resources for {resource_type} only.
 #
 # Explanation:
-# Counts the number of resources that are connected to datasets
-# This gives us a metric for dataset connectivity in the graph
+# Counts the number of resources that are connected to {resource_type}
+# This gives us a metric for {resource_type} connectivity in the graph
 
 SELECT (COUNT(DISTINCT ?connected) as ?numConnectedResources)
 WHERE {
-  ?dataset a {resource_type} ;
+  ?resource a {resource_type} ;
           ?property ?connected .
 
   # Ensure connected resource is not a literal
   FILTER(isIRI(?connected))
-  # Exclude self-references to datasets
-  FILTER(?connected != ?dataset)
+  # Exclude self-references to entities
+  FILTER(?connected != ?resource)
 }
-- 
GitLab


From 6eca29d8f80777d115cedcbf0bfd24aac0273f96 Mon Sep 17 00:00:00 2001
From: Ralf Klammer <ralf.klammer@tu-dresden.de>
Date: Fri, 14 Mar 2025 13:52:24 +0100
Subject: [PATCH 13/59] [metrics] Rename macro 'basic_metrics' to
 'resource_metrics'

---
 .../basic_metrics.md => macros/resource_metrics.md}        | 2 +-
 docs/metrics/resource metrics/06_dataset.md                | 4 ++++
 docs/metrics/resource metrics/07_publication.md            | 7 +++++++
 docs/metrics/resource metrics/08_learning_resource.md      | 4 ++++
 docs/metrics/resource metrics/09_repository.md             | 4 ++++
 docs/metrics/resource metrics/10_article_lhb.md            | 4 ++++
 docs/metrics/resource metrics/11_standards.md              | 4 ++++
 docs/metrics/resource metrics/12_organization.md           | 4 ++++
 docs/metrics/resource metrics/13_software.md               | 4 ++++
 docs/metrics/resource metrics/14_service.md                | 4 ++++
 docs/metrics/resource metrics/15_dataservice.md            | 4 ++++
 docs/metrics/resource metrics/16_aggregator.md             | 4 ++++
 docs/metrics/resource metrics/17_person.md                 | 4 ++++
 docs/metrics/resource metrics/18_registry.md               | 4 ++++
 docs/metrics/resource specific metrics/06_dataset.md       | 4 ----
 docs/metrics/resource specific metrics/07_publication.md   | 4 ----
 .../resource specific metrics/08_learning_resource.md      | 4 ----
 docs/metrics/resource specific metrics/09_repository.md    | 4 ----
 docs/metrics/resource specific metrics/10_article_lhb.md   | 4 ----
 docs/metrics/resource specific metrics/11_standards.md     | 4 ----
 docs/metrics/resource specific metrics/12_organization.md  | 4 ----
 docs/metrics/resource specific metrics/13_software.md      | 4 ----
 docs/metrics/resource specific metrics/14_service.md       | 4 ----
 docs/metrics/resource specific metrics/15_dataservice.md   | 4 ----
 docs/metrics/resource specific metrics/16_aggregator.md    | 4 ----
 docs/metrics/resource specific metrics/17_person.md        | 4 ----
 docs/metrics/resource specific metrics/18_registry.md      | 4 ----
 27 files changed, 56 insertions(+), 53 deletions(-)
 rename docs/{templates/basic_metrics.md => macros/resource_metrics.md} (97%)
 create mode 100644 docs/metrics/resource metrics/06_dataset.md
 create mode 100644 docs/metrics/resource metrics/07_publication.md
 create mode 100644 docs/metrics/resource metrics/08_learning_resource.md
 create mode 100644 docs/metrics/resource metrics/09_repository.md
 create mode 100644 docs/metrics/resource metrics/10_article_lhb.md
 create mode 100644 docs/metrics/resource metrics/11_standards.md
 create mode 100644 docs/metrics/resource metrics/12_organization.md
 create mode 100644 docs/metrics/resource metrics/13_software.md
 create mode 100644 docs/metrics/resource metrics/14_service.md
 create mode 100644 docs/metrics/resource metrics/15_dataservice.md
 create mode 100644 docs/metrics/resource metrics/16_aggregator.md
 create mode 100644 docs/metrics/resource metrics/17_person.md
 create mode 100644 docs/metrics/resource metrics/18_registry.md
 delete mode 100644 docs/metrics/resource specific metrics/06_dataset.md
 delete mode 100644 docs/metrics/resource specific metrics/07_publication.md
 delete mode 100644 docs/metrics/resource specific metrics/08_learning_resource.md
 delete mode 100644 docs/metrics/resource specific metrics/09_repository.md
 delete mode 100644 docs/metrics/resource specific metrics/10_article_lhb.md
 delete mode 100644 docs/metrics/resource specific metrics/11_standards.md
 delete mode 100644 docs/metrics/resource specific metrics/12_organization.md
 delete mode 100644 docs/metrics/resource specific metrics/13_software.md
 delete mode 100644 docs/metrics/resource specific metrics/14_service.md
 delete mode 100644 docs/metrics/resource specific metrics/15_dataservice.md
 delete mode 100644 docs/metrics/resource specific metrics/16_aggregator.md
 delete mode 100644 docs/metrics/resource specific metrics/17_person.md
 delete mode 100644 docs/metrics/resource specific metrics/18_registry.md

diff --git a/docs/templates/basic_metrics.md b/docs/macros/resource_metrics.md
similarity index 97%
rename from docs/templates/basic_metrics.md
rename to docs/macros/resource_metrics.md
index 8184739..77097ed 100644
--- a/docs/templates/basic_metrics.md
+++ b/docs/macros/resource_metrics.md
@@ -1,4 +1,4 @@
-{% macro basic_metrics(resource_type) %}
+{% macro resource_metrics(resource_type) %}
 
 Resource type: {{resource_type}}
 
diff --git a/docs/metrics/resource metrics/06_dataset.md b/docs/metrics/resource metrics/06_dataset.md
new file mode 100644
index 0000000..1420357
--- /dev/null
+++ b/docs/metrics/resource metrics/06_dataset.md	
@@ -0,0 +1,4 @@
+{% from "macros/resource_metrics.md" import resource_metrics with context %}
+# Dataset
+
+{{ resource_metrics("<http://www.w3.org/ns/dcat#Dataset>") }}
diff --git a/docs/metrics/resource metrics/07_publication.md b/docs/metrics/resource metrics/07_publication.md
new file mode 100644
index 0000000..db3cf51
--- /dev/null
+++ b/docs/metrics/resource metrics/07_publication.md	
@@ -0,0 +1,7 @@
+{% from "macros/resource_metrics.md" import resource_metrics with context %}
+# Publication
+
+{{ resource_metrics("<SELECT (COUNT(DISTINCT ?resource) AS ?resourceCount)
+WHERE {
+  ?resource a <http://nfdi4earth.de/ontology/Registry> .
+}>") }}
diff --git a/docs/metrics/resource metrics/08_learning_resource.md b/docs/metrics/resource metrics/08_learning_resource.md
new file mode 100644
index 0000000..cdc7eff
--- /dev/null
+++ b/docs/metrics/resource metrics/08_learning_resource.md	
@@ -0,0 +1,4 @@
+{% from "macros/resource_metrics.md" import resource_metrics with context %}
+# Learning Resource
+
+{{ resource_metrics("<http://schema.org/LearningResource>") }}
diff --git a/docs/metrics/resource metrics/09_repository.md b/docs/metrics/resource metrics/09_repository.md
new file mode 100644
index 0000000..fb9a763
--- /dev/null
+++ b/docs/metrics/resource metrics/09_repository.md	
@@ -0,0 +1,4 @@
+{% from "macros/resource_metrics.md" import resource_metrics with context %}
+# Repository
+
+{{ resource_metrics("<http://nfdi4earth.de/ontology/Repository>") }}
diff --git a/docs/metrics/resource metrics/10_article_lhb.md b/docs/metrics/resource metrics/10_article_lhb.md
new file mode 100644
index 0000000..103c445
--- /dev/null
+++ b/docs/metrics/resource metrics/10_article_lhb.md	
@@ -0,0 +1,4 @@
+{% from "macros/resource_metrics.md" import resource_metrics with context %}
+# Living Handbook Article
+
+{{ resource_metrics("<http://nfdi4earth.de/ontology/LHBArticle>") }}
diff --git a/docs/metrics/resource metrics/11_standards.md b/docs/metrics/resource metrics/11_standards.md
new file mode 100644
index 0000000..4b2a8ef
--- /dev/null
+++ b/docs/metrics/resource metrics/11_standards.md	
@@ -0,0 +1,4 @@
+{% from "macros/resource_metrics.md" import resource_metrics with context %}
+# Standards
+
+{{ resource_metrics("<http://nfdi4earth.de/ontology/MetadataStandard>") }}
diff --git a/docs/metrics/resource metrics/12_organization.md b/docs/metrics/resource metrics/12_organization.md
new file mode 100644
index 0000000..7c2b9d5
--- /dev/null
+++ b/docs/metrics/resource metrics/12_organization.md	
@@ -0,0 +1,4 @@
+{% from "macros/resource_metrics.md" import resource_metrics with context %}
+# Organization
+
+{{ resource_metrics("<http://xmlns.com/foaf/0.1/Organization>") }}
diff --git a/docs/metrics/resource metrics/13_software.md b/docs/metrics/resource metrics/13_software.md
new file mode 100644
index 0000000..10e03f1
--- /dev/null
+++ b/docs/metrics/resource metrics/13_software.md	
@@ -0,0 +1,4 @@
+{% from "macros/resource_metrics.md" import resource_metrics with context %}
+# Tools & Software
+
+{{ resource_metrics("<http://schema.org/SoftwareSourceCode>") }}
diff --git a/docs/metrics/resource metrics/14_service.md b/docs/metrics/resource metrics/14_service.md
new file mode 100644
index 0000000..73c8999
--- /dev/null
+++ b/docs/metrics/resource metrics/14_service.md	
@@ -0,0 +1,4 @@
+{% from "macros/resource_metrics.md" import resource_metrics with context %}
+# Service
+
+{{ resource_metrics("<http://www.w3.org/ns/sparql-service-description#Service>") }}
diff --git a/docs/metrics/resource metrics/15_dataservice.md b/docs/metrics/resource metrics/15_dataservice.md
new file mode 100644
index 0000000..06cd911
--- /dev/null
+++ b/docs/metrics/resource metrics/15_dataservice.md	
@@ -0,0 +1,4 @@
+{% from "macros/resource_metrics.md" import resource_metrics with context %}
+# Data Service
+
+{{ resource_metrics("<http://www.w3.org/ns/dcat#DataService>") }}
diff --git a/docs/metrics/resource metrics/16_aggregator.md b/docs/metrics/resource metrics/16_aggregator.md
new file mode 100644
index 0000000..c0e794b
--- /dev/null
+++ b/docs/metrics/resource metrics/16_aggregator.md	
@@ -0,0 +1,4 @@
+{% from "macros/resource_metrics.md" import resource_metrics with context %}
+# Aggregator
+
+{{ resource_metrics("<http://nfdi4earth.de/ontology/Aggregator>") }}
diff --git a/docs/metrics/resource metrics/17_person.md b/docs/metrics/resource metrics/17_person.md
new file mode 100644
index 0000000..f220c51
--- /dev/null
+++ b/docs/metrics/resource metrics/17_person.md	
@@ -0,0 +1,4 @@
+{% from "macros/resource_metrics.md" import resource_metrics with context %}
+# Person
+
+{{ resource_metrics("<http://schema.org/Person>") }}
diff --git a/docs/metrics/resource metrics/18_registry.md b/docs/metrics/resource metrics/18_registry.md
new file mode 100644
index 0000000..8b81796
--- /dev/null
+++ b/docs/metrics/resource metrics/18_registry.md	
@@ -0,0 +1,4 @@
+{% from "macros/resource_metrics.md" import resource_metrics with context %}
+# Registry
+
+{{ resource_metrics("<http://nfdi4earth.de/ontology/Registry>") }}
diff --git a/docs/metrics/resource specific metrics/06_dataset.md b/docs/metrics/resource specific metrics/06_dataset.md
deleted file mode 100644
index fbbb7c0..0000000
--- a/docs/metrics/resource specific metrics/06_dataset.md	
+++ /dev/null
@@ -1,4 +0,0 @@
-{% from "templates/basic_metrics.md" import basic_metrics with context %}
-# Dataset
-
-{{ basic_metrics("<http://www.w3.org/ns/dcat#Dataset>") }}
diff --git a/docs/metrics/resource specific metrics/07_publication.md b/docs/metrics/resource specific metrics/07_publication.md
deleted file mode 100644
index 46a7d3d..0000000
--- a/docs/metrics/resource specific metrics/07_publication.md	
+++ /dev/null
@@ -1,4 +0,0 @@
-{% from "templates/basic_metrics.md" import basic_metrics with context %}
-# Publication
-
-{{ basic_metrics("<http://nfdi4earth.de/ontology/Publication>") }}
diff --git a/docs/metrics/resource specific metrics/08_learning_resource.md b/docs/metrics/resource specific metrics/08_learning_resource.md
deleted file mode 100644
index e9d05b5..0000000
--- a/docs/metrics/resource specific metrics/08_learning_resource.md	
+++ /dev/null
@@ -1,4 +0,0 @@
-{% from "templates/basic_metrics.md" import basic_metrics with context %}
-# Learning Resource
-
-{{ basic_metrics("<http://schema.org/LearningResource>") }}
diff --git a/docs/metrics/resource specific metrics/09_repository.md b/docs/metrics/resource specific metrics/09_repository.md
deleted file mode 100644
index a1eb845..0000000
--- a/docs/metrics/resource specific metrics/09_repository.md	
+++ /dev/null
@@ -1,4 +0,0 @@
-{% from "templates/basic_metrics.md" import basic_metrics with context %}
-# Repository
-
-{{ basic_metrics("<http://nfdi4earth.de/ontology/Repository>") }}
diff --git a/docs/metrics/resource specific metrics/10_article_lhb.md b/docs/metrics/resource specific metrics/10_article_lhb.md
deleted file mode 100644
index e37dca7..0000000
--- a/docs/metrics/resource specific metrics/10_article_lhb.md	
+++ /dev/null
@@ -1,4 +0,0 @@
-{% from "templates/basic_metrics.md" import basic_metrics with context %}
-# Living Handbook Article
-
-{{ basic_metrics("<http://nfdi4earth.de/ontology/LHBArticle>") }}
diff --git a/docs/metrics/resource specific metrics/11_standards.md b/docs/metrics/resource specific metrics/11_standards.md
deleted file mode 100644
index f09b4b5..0000000
--- a/docs/metrics/resource specific metrics/11_standards.md	
+++ /dev/null
@@ -1,4 +0,0 @@
-{% from "templates/basic_metrics.md" import basic_metrics with context %}
-# Standards
-
-{{ basic_metrics("<http://nfdi4earth.de/ontology/MetadataStandard>") }}
diff --git a/docs/metrics/resource specific metrics/12_organization.md b/docs/metrics/resource specific metrics/12_organization.md
deleted file mode 100644
index 61e6323..0000000
--- a/docs/metrics/resource specific metrics/12_organization.md	
+++ /dev/null
@@ -1,4 +0,0 @@
-{% from "templates/basic_metrics.md" import basic_metrics with context %}
-# Organization
-
-{{ basic_metrics("<http://xmlns.com/foaf/0.1/Organization>") }}
diff --git a/docs/metrics/resource specific metrics/13_software.md b/docs/metrics/resource specific metrics/13_software.md
deleted file mode 100644
index bb23391..0000000
--- a/docs/metrics/resource specific metrics/13_software.md	
+++ /dev/null
@@ -1,4 +0,0 @@
-{% from "templates/basic_metrics.md" import basic_metrics with context %}
-# Tools & Software
-
-{{ basic_metrics("<http://schema.org/SoftwareSourceCode>") }}
diff --git a/docs/metrics/resource specific metrics/14_service.md b/docs/metrics/resource specific metrics/14_service.md
deleted file mode 100644
index 48efc3e..0000000
--- a/docs/metrics/resource specific metrics/14_service.md	
+++ /dev/null
@@ -1,4 +0,0 @@
-{% from "templates/basic_metrics.md" import basic_metrics with context %}
-# Service
-
-{{ basic_metrics("<http://www.w3.org/ns/sparql-service-description#Service>") }}
diff --git a/docs/metrics/resource specific metrics/15_dataservice.md b/docs/metrics/resource specific metrics/15_dataservice.md
deleted file mode 100644
index 82c9a69..0000000
--- a/docs/metrics/resource specific metrics/15_dataservice.md	
+++ /dev/null
@@ -1,4 +0,0 @@
-{% from "templates/basic_metrics.md" import basic_metrics with context %}
-# Data Service
-
-{{ basic_metrics("<http://www.w3.org/ns/dcat#DataService>") }}
diff --git a/docs/metrics/resource specific metrics/16_aggregator.md b/docs/metrics/resource specific metrics/16_aggregator.md
deleted file mode 100644
index 114bcfa..0000000
--- a/docs/metrics/resource specific metrics/16_aggregator.md	
+++ /dev/null
@@ -1,4 +0,0 @@
-{% from "templates/basic_metrics.md" import basic_metrics with context %}
-# Aggregator
-
-{{ basic_metrics("<http://nfdi4earth.de/ontology/Aggregator>") }}
diff --git a/docs/metrics/resource specific metrics/17_person.md b/docs/metrics/resource specific metrics/17_person.md
deleted file mode 100644
index d806fab..0000000
--- a/docs/metrics/resource specific metrics/17_person.md	
+++ /dev/null
@@ -1,4 +0,0 @@
-{% from "templates/basic_metrics.md" import basic_metrics with context %}
-# Person
-
-{{ basic_metrics("<http://schema.org/Person>") }}
diff --git a/docs/metrics/resource specific metrics/18_registry.md b/docs/metrics/resource specific metrics/18_registry.md
deleted file mode 100644
index bfb1803..0000000
--- a/docs/metrics/resource specific metrics/18_registry.md	
+++ /dev/null
@@ -1,4 +0,0 @@
-{% from "templates/basic_metrics.md" import basic_metrics with context %}
-# Registry
-
-{{ basic_metrics("<http://nfdi4earth.de/ontology/Registry>") }}
-- 
GitLab


From 0db211cefbd32084ab2a79ec7ba509ff9f281642 Mon Sep 17 00:00:00 2001
From: Ralf Klammer <ralf.klammer@tu-dresden.de>
Date: Fri, 14 Mar 2025 14:05:52 +0100
Subject: [PATCH 14/59] [metrics][kg_analysis] Cleanup

---
 scripts/kg_analysis/metrics_runner.py | 22 +---------------------
 1 file changed, 1 insertion(+), 21 deletions(-)

diff --git a/scripts/kg_analysis/metrics_runner.py b/scripts/kg_analysis/metrics_runner.py
index 6f59afe..206192d 100644
--- a/scripts/kg_analysis/metrics_runner.py
+++ b/scripts/kg_analysis/metrics_runner.py
@@ -244,14 +244,6 @@ class MetricsRunner_Resources(MetricsRunnerBase):
                 },
             },
         },
-        # "edges_out": {
-        #     "name": "Edges - outgoing",
-        #     "file": "RM004_out_edges_template.rq",
-        # "execute": True,  # },
-        # "edges_in": {
-        #     "name": "Edges - incoming",
-        #     "file": "RM005_in_edges_template.rq",
-        # "execute": True,  # },
         "connectivity": {
             "name": "Connectivity",
             "file": "RM006_connectivity_template.rq",
@@ -331,24 +323,12 @@ class MetricsRunner_Resources(MetricsRunnerBase):
         for resource_type, data in self.resource_types.items():
             log.info(f"Analyzing: {data["uri"]}")
             queries = deepcopy(self.query_templates)
-            for metric, query in queries.items():
-                # if metric not in ["instances", "edges_out"]:
-                #     continue
-                # print(query)
+            for query in queries.values():
                 if "files" in query:
-                    print(query["files"])
                     for sub_query in query["files"].values():
                         self._get_result(sub_query, data["uri"])
                 else:
                     self._get_result(query, data["uri"])
-                # result = self.query_metric(
-                #     self.get_query_path(query["file"]),
-                #     replace_dict={
-                #         "{resource_type}": data["uri"],
-                #     },
-                # )
-                # query["result"] = result
-                # query["execution_time"] = round(self._execution_time, 2)
             self.resource_types[resource_type]["queries"] = queries
         rprint(self.resource_types)
 
-- 
GitLab


From 910d4af932ccf48a7a9e0cbe348ad16e914e8461 Mon Sep 17 00:00:00 2001
From: Ralf Klammer <ralf.klammer@tu-dresden.de>
Date: Fri, 14 Mar 2025 14:57:30 +0100
Subject: [PATCH 15/59] [metrics] fix: 07_publication.md

---
 docs/metrics/resource metrics/07_publication.md | 5 +----
 1 file changed, 1 insertion(+), 4 deletions(-)

diff --git a/docs/metrics/resource metrics/07_publication.md b/docs/metrics/resource metrics/07_publication.md
index db3cf51..9e6ee28 100644
--- a/docs/metrics/resource metrics/07_publication.md	
+++ b/docs/metrics/resource metrics/07_publication.md	
@@ -1,7 +1,4 @@
 {% from "macros/resource_metrics.md" import resource_metrics with context %}
 # Publication
 
-{{ resource_metrics("<SELECT (COUNT(DISTINCT ?resource) AS ?resourceCount)
-WHERE {
-  ?resource a <http://nfdi4earth.de/ontology/Registry> .
-}>") }}
+{{ resource_metrics("<http://nfdi4earth.de/ontology/Publication>") }}
-- 
GitLab


From 39b8276871608b6f84aadac348dc97bedb35c0ef Mon Sep 17 00:00:00 2001
From: Ralf Klammer <ralf.klammer@tu-dresden.de>
Date: Fri, 14 Mar 2025 16:24:50 +0100
Subject: [PATCH 16/59] [metrics][kg_analysis] Export resource metrics to
 *.json and render them via macro

---
 docs/macros/main.py                           |   61 +
 docs/macros/resource_metrics.md               |   31 +-
 docs/metrics/resource metrics/06_dataset.md   |    2 +-
 .../resource metrics/07_publication.md        |    2 +-
 .../resource metrics/08_learning_resource.md  |    2 +-
 .../metrics/resource metrics/09_repository.md |    2 +-
 .../resource metrics/10_article_lhb.md        |    2 +-
 docs/metrics/resource metrics/11_standards.md |    2 +-
 .../resource metrics/12_organization.md       |    2 +-
 docs/metrics/resource metrics/13_software.md  |    2 +-
 docs/metrics/resource metrics/14_service.md   |    2 +-
 .../resource metrics/15_dataservice.md        |    2 +-
 .../metrics/resource metrics/16_aggregator.md |    2 +-
 docs/metrics/resource metrics/17_person.md    |    2 +-
 docs/metrics/resource metrics/18_registry.md  |    2 +-
 reports/metrics/resources.json                | 1275 +++++++++++++++++
 scripts/kg_analysis/metrics_runner.py         |  170 ++-
 17 files changed, 1518 insertions(+), 45 deletions(-)
 create mode 100644 reports/metrics/resources.json

diff --git a/docs/macros/main.py b/docs/macros/main.py
index 9b2e345..88446fe 100644
--- a/docs/macros/main.py
+++ b/docs/macros/main.py
@@ -1,3 +1,7 @@
+import json
+from pathlib import Path
+
+
 def define_env(env):
     @env.macro
     def include_if_exists(filename, start_line=None, single_line=None):
@@ -27,3 +31,60 @@ def define_env(env):
         if content:
             return content.replace("{resource_type}", resource_type)
         return ""
+
+    def render_resource(resource_data, output, resource_type=None):
+        """Helper function to render a single resource"""
+        for query_data in resource_data["queries"].values():
+            if "result" in query_data:
+                if resource_type:
+                    output.append(
+                        f"| {resource_type} | {query_data['name']} | {query_data['result']} |"
+                    )
+                else:
+                    output.append(f"| {query_data['name']} | {query_data['result']} |")
+            else:
+                if resource_type:
+                    output.append(f"| {resource_type} | **{query_data['name']}** | |")
+                else:
+                    output.append(f"| **{query_data['name']}** | |")
+                for sub_query_data in query_data["files"].values():
+                    if resource_type:
+                        output.append(
+                            f"| {resource_type} | {sub_query_data['name']} | {sub_query_data['result']} |"
+                        )
+                    else:
+                        output.append(
+                            f"| {sub_query_data['name']} | {sub_query_data['result']} |"
+                        )
+
+    @env.macro
+    def resource_metrics_table(resource_type=None):
+        """Renders metrics as markdown table for one or all resource types"""
+        try:
+            metrics_file = Path("reports/metrics/resources.json")
+            with open(metrics_file, "r", encoding="utf-8") as f:
+                resources = json.load(f)
+        except (FileNotFoundError, json.JSONDecodeError) as e:
+            return f"*Error loading metrics: {e}*"
+
+        output = []
+        timestamp = resources.get("timestamp", "unknown")
+        output.append(f"*Last updated: {timestamp}*\n")
+
+        if resource_type:
+            # Render single resource
+            if resource_type not in resources:
+                return f"*No metrics found for {resource_type}*"
+
+            output.append("| Metric | Result |")
+            output.append("|--------|--------|")
+            render_resource(resources[resource_type], output)
+        else:
+            # Render all resources
+            output.append("| Resource Type | Metric | Result |")
+            output.append("|--------------|--------|--------|")
+            for res_type, resource_data in resources.items():
+                if res_type != "timestamp":
+                    render_resource(resource_data, output, res_type)
+
+        return "\n".join(output)
diff --git a/docs/macros/resource_metrics.md b/docs/macros/resource_metrics.md
index 77097ed..4aa3b47 100644
--- a/docs/macros/resource_metrics.md
+++ b/docs/macros/resource_metrics.md
@@ -1,6 +1,9 @@
-{% macro resource_metrics(resource_type) %}
+{% macro resource_metrics(resource_type, resource_type_uri) %}
 
-Resource type: {{resource_type}}
+Resource type: {{resource_type_uri}}
+
+## Results
+{{ resource_metrics_table(resource_type) }}
 
 ## Basic Metrics
 
@@ -9,13 +12,13 @@ Resource type: {{resource_type}}
 see also: [general metrics/instances](/metrics/general%20metrics/01_instances/)
 
 ```sparql
-{{ include_template("queries/metrics/RM001_instances_template.rq", resource_type) }}
+{{ include_template("queries/metrics/RM001_instances_template.rq", resource_type_uri) }}
 ```
 
 ### Connectivity to other resources
 
 ```sparql
-{{ include_template("queries/metrics/RM006_connectivity_template.rq", resource_type) }}
+{{ include_template("queries/metrics/RM006_connectivity_template.rq", resource_type_uri) }}
 ```
 
 ### Number of Assertions
@@ -23,7 +26,7 @@ see also: [general metrics/instances](/metrics/general%20metrics/01_instances/)
 see also: [general metrics/assortions](/metrics/general%20metrics/02_assertions/)
 
 ```sparql
-{{ include_template("queries/metrics/RM002_assertions_template.rq", resource_type) }}
+{{ include_template("queries/metrics/RM002_assertions_template.rq", resource_type_uri) }}
 ```
 
 ### Average linkage
@@ -31,7 +34,7 @@ see also: [general metrics/assortions](/metrics/general%20metrics/02_assertions/
 see also: [general metrics/linkage](/metrics/general%20metrics/03_linkage_degree/)
 
 ```sparql
-{{ include_template("queries/metrics/RM003_linkage_template.rq", resource_type) }}
+{{ include_template("queries/metrics/RM003_linkage_template.rq", resource_type_uri) }}
 ```
 
 ### Outgoing Edges Statistics
@@ -41,25 +44,25 @@ see also: [general metrics/outgoing edges](/metrics/general%20metrics/04_outgoin
 #### Total outgoing edges
 
 ```sparql
-{{ include_template("queries/metrics/RM004_1_out_edges_total_template.rq", resource_type) }}
+{{ include_template("queries/metrics/RM004_1_out_edges_total_template.rq", resource_type_uri) }}
 ```
 
 #### Minimum outgoing edges
 
 ```sparql
-{{ include_template("queries/metrics/RM004_2_out_edges_min_template.rq", resource_type) }}
+{{ include_template("queries/metrics/RM004_2_out_edges_min_template.rq", resource_type_uri) }}
 ```
 
 #### Median outgoing edges
 
 ```sparql
-{{ include_template("queries/metrics/RM004_3_out_edges_median_template.rq", resource_type) }}
+{{ include_template("queries/metrics/RM004_3_out_edges_median_template.rq", resource_type_uri) }}
 ```
 
 #### Maximum outgoing edges
 
 ```sparql
-{{ include_template("queries/metrics/RM004_4_out_edges_max_template.rq", resource_type) }}
+{{ include_template("queries/metrics/RM004_4_out_edges_max_template.rq", resource_type_uri) }}
 ```
 
 ### Incoming Edges Statistics
@@ -69,25 +72,25 @@ see also: [general metrics/incoming edges](/metrics/general%20metrics/05_incomin
 #### Total incoming edges
 
 ```sparql
-{{ include_template("queries/metrics/RM005_1_in_edges_total_template.rq", resource_type) }}
+{{ include_template("queries/metrics/RM005_1_in_edges_total_template.rq", resource_type_uri) }}
 ```
 
 #### Minimum incoming edges
 
 ```sparql
-{{ include_template("queries/metrics/RM005_2_in_edges_min_template.rq", resource_type) }}
+{{ include_template("queries/metrics/RM005_2_in_edges_min_template.rq", resource_type_uri) }}
 ```
 
 #### Median incoming edges
 
 ```sparql
-{{ include_template("queries/metrics/RM005_3_in_edges_median_template.rq", resource_type) }}
+{{ include_template("queries/metrics/RM005_3_in_edges_median_template.rq", resource_type_uri) }}
 ```
 
 #### Maximum incoming edges
 
 ```sparql
-{{ include_template("queries/metrics/RM005_4_in_edges_max_template.rq", resource_type) }}
+{{ include_template("queries/metrics/RM005_4_in_edges_max_template.rq", resource_type_uri) }}
 ```
 
 {% endmacro %}
diff --git a/docs/metrics/resource metrics/06_dataset.md b/docs/metrics/resource metrics/06_dataset.md
index 1420357..5d52a5f 100644
--- a/docs/metrics/resource metrics/06_dataset.md	
+++ b/docs/metrics/resource metrics/06_dataset.md	
@@ -1,4 +1,4 @@
 {% from "macros/resource_metrics.md" import resource_metrics with context %}
 # Dataset
 
-{{ resource_metrics("<http://www.w3.org/ns/dcat#Dataset>") }}
+{{ resource_metrics("dataset", "<http://www.w3.org/ns/dcat#Dataset>") }}
diff --git a/docs/metrics/resource metrics/07_publication.md b/docs/metrics/resource metrics/07_publication.md
index 9e6ee28..d873284 100644
--- a/docs/metrics/resource metrics/07_publication.md	
+++ b/docs/metrics/resource metrics/07_publication.md	
@@ -1,4 +1,4 @@
 {% from "macros/resource_metrics.md" import resource_metrics with context %}
 # Publication
 
-{{ resource_metrics("<http://nfdi4earth.de/ontology/Publication>") }}
+{{ resource_metrics("publication", "<http://nfdi4earth.de/ontology/Publication>") }}
diff --git a/docs/metrics/resource metrics/08_learning_resource.md b/docs/metrics/resource metrics/08_learning_resource.md
index cdc7eff..a4246ce 100644
--- a/docs/metrics/resource metrics/08_learning_resource.md	
+++ b/docs/metrics/resource metrics/08_learning_resource.md	
@@ -1,4 +1,4 @@
 {% from "macros/resource_metrics.md" import resource_metrics with context %}
 # Learning Resource
 
-{{ resource_metrics("<http://schema.org/LearningResource>") }}
+{{ resource_metrics("learning_resource", "<http://schema.org/LearningResource>") }}
diff --git a/docs/metrics/resource metrics/09_repository.md b/docs/metrics/resource metrics/09_repository.md
index fb9a763..6593bba 100644
--- a/docs/metrics/resource metrics/09_repository.md	
+++ b/docs/metrics/resource metrics/09_repository.md	
@@ -1,4 +1,4 @@
 {% from "macros/resource_metrics.md" import resource_metrics with context %}
 # Repository
 
-{{ resource_metrics("<http://nfdi4earth.de/ontology/Repository>") }}
+{{ resource_metrics("repository", "<http://nfdi4earth.de/ontology/Repository>") }}
diff --git a/docs/metrics/resource metrics/10_article_lhb.md b/docs/metrics/resource metrics/10_article_lhb.md
index 103c445..8ebe52a 100644
--- a/docs/metrics/resource metrics/10_article_lhb.md	
+++ b/docs/metrics/resource metrics/10_article_lhb.md	
@@ -1,4 +1,4 @@
 {% from "macros/resource_metrics.md" import resource_metrics with context %}
 # Living Handbook Article
 
-{{ resource_metrics("<http://nfdi4earth.de/ontology/LHBArticle>") }}
+{{ resource_metrics("article_lhb", "<http://nfdi4earth.de/ontology/LHBArticle>") }}
diff --git a/docs/metrics/resource metrics/11_standards.md b/docs/metrics/resource metrics/11_standards.md
index 4b2a8ef..c895977 100644
--- a/docs/metrics/resource metrics/11_standards.md	
+++ b/docs/metrics/resource metrics/11_standards.md	
@@ -1,4 +1,4 @@
 {% from "macros/resource_metrics.md" import resource_metrics with context %}
 # Standards
 
-{{ resource_metrics("<http://nfdi4earth.de/ontology/MetadataStandard>") }}
+{{ resource_metrics("standards", "<http://nfdi4earth.de/ontology/MetadataStandard>") }}
diff --git a/docs/metrics/resource metrics/12_organization.md b/docs/metrics/resource metrics/12_organization.md
index 7c2b9d5..42c1d8e 100644
--- a/docs/metrics/resource metrics/12_organization.md	
+++ b/docs/metrics/resource metrics/12_organization.md	
@@ -1,4 +1,4 @@
 {% from "macros/resource_metrics.md" import resource_metrics with context %}
 # Organization
 
-{{ resource_metrics("<http://xmlns.com/foaf/0.1/Organization>") }}
+{{ resource_metrics("organization", "<http://xmlns.com/foaf/0.1/Organization>") }}
diff --git a/docs/metrics/resource metrics/13_software.md b/docs/metrics/resource metrics/13_software.md
index 10e03f1..6210dea 100644
--- a/docs/metrics/resource metrics/13_software.md	
+++ b/docs/metrics/resource metrics/13_software.md	
@@ -1,4 +1,4 @@
 {% from "macros/resource_metrics.md" import resource_metrics with context %}
 # Tools & Software
 
-{{ resource_metrics("<http://schema.org/SoftwareSourceCode>") }}
+{{ resource_metrics("software", "<http://schema.org/SoftwareSourceCode>") }}
diff --git a/docs/metrics/resource metrics/14_service.md b/docs/metrics/resource metrics/14_service.md
index 73c8999..b58e404 100644
--- a/docs/metrics/resource metrics/14_service.md	
+++ b/docs/metrics/resource metrics/14_service.md	
@@ -1,4 +1,4 @@
 {% from "macros/resource_metrics.md" import resource_metrics with context %}
 # Service
 
-{{ resource_metrics("<http://www.w3.org/ns/sparql-service-description#Service>") }}
+{{ resource_metrics("service", "<http://www.w3.org/ns/sparql-service-description#Service>") }}
diff --git a/docs/metrics/resource metrics/15_dataservice.md b/docs/metrics/resource metrics/15_dataservice.md
index 06cd911..a102d74 100644
--- a/docs/metrics/resource metrics/15_dataservice.md	
+++ b/docs/metrics/resource metrics/15_dataservice.md	
@@ -1,4 +1,4 @@
 {% from "macros/resource_metrics.md" import resource_metrics with context %}
 # Data Service
 
-{{ resource_metrics("<http://www.w3.org/ns/dcat#DataService>") }}
+{{ resource_metrics("dataservice", "<http://www.w3.org/ns/dcat#DataService>") }}
diff --git a/docs/metrics/resource metrics/16_aggregator.md b/docs/metrics/resource metrics/16_aggregator.md
index c0e794b..e40651d 100644
--- a/docs/metrics/resource metrics/16_aggregator.md	
+++ b/docs/metrics/resource metrics/16_aggregator.md	
@@ -1,4 +1,4 @@
 {% from "macros/resource_metrics.md" import resource_metrics with context %}
 # Aggregator
 
-{{ resource_metrics("<http://nfdi4earth.de/ontology/Aggregator>") }}
+{{ resource_metrics("aggregator", "<http://nfdi4earth.de/ontology/Aggregator>") }}
diff --git a/docs/metrics/resource metrics/17_person.md b/docs/metrics/resource metrics/17_person.md
index f220c51..09b3e64 100644
--- a/docs/metrics/resource metrics/17_person.md	
+++ b/docs/metrics/resource metrics/17_person.md	
@@ -1,4 +1,4 @@
 {% from "macros/resource_metrics.md" import resource_metrics with context %}
 # Person
 
-{{ resource_metrics("<http://schema.org/Person>") }}
+{{ resource_metrics("person", "<http://schema.org/Person>") }}
diff --git a/docs/metrics/resource metrics/18_registry.md b/docs/metrics/resource metrics/18_registry.md
index 8b81796..499b9a2 100644
--- a/docs/metrics/resource metrics/18_registry.md	
+++ b/docs/metrics/resource metrics/18_registry.md	
@@ -1,4 +1,4 @@
 {% from "macros/resource_metrics.md" import resource_metrics with context %}
 # Registry
 
-{{ resource_metrics("<http://nfdi4earth.de/ontology/Registry>") }}
+{{ resource_metrics("registry", "<http://nfdi4earth.de/ontology/Registry>") }}
diff --git a/reports/metrics/resources.json b/reports/metrics/resources.json
new file mode 100644
index 0000000..89dfba1
--- /dev/null
+++ b/reports/metrics/resources.json
@@ -0,0 +1,1275 @@
+{
+    "dataset": {
+        "file": "06_dataset.md",
+        "uri": "<http://www.w3.org/ns/dcat#Dataset>",
+        "queries": {
+            "instances": {
+                "name": "Number of Resources",
+                "file": "RM001_instances_template.rq",
+                "execute": true,
+                "result": "724903",
+                "execution_time": 0.52
+            },
+            "assertions": {
+                "name": "Number of Assertions",
+                "file": "RM002_assertions_template.rq",
+                "execute": true,
+                "result": "724904",
+                "execution_time": 0.02
+            },
+            "linkage": {
+                "name": "Average Linkage",
+                "file": "RM003_linkage_template.rq",
+                "execute": true,
+                "result": "68",
+                "execution_time": 0.07
+            },
+            "edges_out": {
+                "name": "Edges - outgoing",
+                "files": {
+                    "total": {
+                        "name": "Total number of outgoing edges",
+                        "file": "RM004_1_out_edges_total_template.rq",
+                        "execute": true,
+                        "result": "724903",
+                        "execution_time": 4.03
+                    },
+                    "min": {
+                        "name": "Minimum of outgoing edges",
+                        "file": "RM004_2_out_edges_min_template.rq",
+                        "execute": true,
+                        "result": "6",
+                        "execution_time": 0.83
+                    },
+                    "median": {
+                        "name": "Median of outgoing edges",
+                        "file": "RM004_3_out_edges_median_template.rq",
+                        "replace_dict": {
+                            "{median_position}": "RM004_1_out_edges_total_template.rq"
+                        },
+                        "execute": true,
+                        "result": "23",
+                        "execution_time": 1.46
+                    },
+                    "max": {
+                        "name": "Maximum of outgoing edges",
+                        "file": "RM004_4_out_edges_max_template.rq",
+                        "execute": true,
+                        "result": "12519",
+                        "execution_time": 0.78
+                    }
+                }
+            },
+            "edges_in": {
+                "name": "Edges - incoming",
+                "files": {
+                    "total": {
+                        "name": "Total number of incoming edges",
+                        "file": "RM005_1_in_edges_total_template.rq",
+                        "execute": true,
+                        "result": "4",
+                        "execution_time": 0.12
+                    },
+                    "min": {
+                        "name": "Minimum of incoming edges",
+                        "file": "RM005_2_in_edges_min_template.rq",
+                        "execute": true,
+                        "result": "1",
+                        "execution_time": 0.05
+                    },
+                    "median": {
+                        "name": "Median of incoming edges",
+                        "file": "RM005_3_in_edges_median_template.rq",
+                        "replace_dict": {
+                            "{median_position}": "RM005_1_in_edges_total_template.rq"
+                        },
+                        "execute": true,
+                        "result": "1",
+                        "execution_time": 0.05
+                    },
+                    "max": {
+                        "name": "Maximum of incoming edges",
+                        "file": "RM005_4_in_edges_max_template.rq",
+                        "execute": true,
+                        "result": "107",
+                        "execution_time": 1.1
+                    }
+                }
+            },
+            "connectivity": {
+                "name": "Connectivity",
+                "file": "RM006_connectivity_template.rq",
+                "execute": true,
+                "result": "427594",
+                "execution_time": 4.33
+            }
+        }
+    },
+    "publication": {
+        "file": "07_publication.md",
+        "uri": "<http://nfdi4earth.de/ontology/Registry>",
+        "queries": {
+            "instances": {
+                "name": "Number of Resources",
+                "file": "RM001_instances_template.rq",
+                "execute": true,
+                "result": "5",
+                "execution_time": 0.01
+            },
+            "assertions": {
+                "name": "Number of Assertions",
+                "file": "RM002_assertions_template.rq",
+                "execute": true,
+                "result": "6",
+                "execution_time": 0.01
+            },
+            "linkage": {
+                "name": "Average Linkage",
+                "file": "RM003_linkage_template.rq",
+                "execute": true,
+                "result": 0,
+                "execution_time": 0.02
+            },
+            "edges_out": {
+                "name": "Edges - outgoing",
+                "files": {
+                    "total": {
+                        "name": "Total number of outgoing edges",
+                        "file": "RM004_1_out_edges_total_template.rq",
+                        "execute": true,
+                        "result": "5",
+                        "execution_time": 0.01
+                    },
+                    "min": {
+                        "name": "Minimum of outgoing edges",
+                        "file": "RM004_2_out_edges_min_template.rq",
+                        "execute": true,
+                        "result": "7",
+                        "execution_time": 0.02
+                    },
+                    "median": {
+                        "name": "Median of outgoing edges",
+                        "file": "RM004_3_out_edges_median_template.rq",
+                        "replace_dict": {
+                            "{median_position}": "RM004_1_out_edges_total_template.rq"
+                        },
+                        "execute": true,
+                        "result": "9",
+                        "execution_time": 0.02
+                    },
+                    "max": {
+                        "name": "Maximum of outgoing edges",
+                        "file": "RM004_4_out_edges_max_template.rq",
+                        "execute": true,
+                        "result": "11",
+                        "execution_time": 0.01
+                    }
+                }
+            },
+            "edges_in": {
+                "name": "Edges - incoming",
+                "files": {
+                    "total": {
+                        "name": "Total number of incoming edges",
+                        "file": "RM005_1_in_edges_total_template.rq",
+                        "execute": true,
+                        "result": "0",
+                        "execution_time": 0.02
+                    },
+                    "min": {
+                        "name": "Minimum of incoming edges",
+                        "file": "RM005_2_in_edges_min_template.rq",
+                        "execute": true,
+                        "result": "-",
+                        "execution_time": 0.01
+                    },
+                    "median": {
+                        "name": "Median of incoming edges",
+                        "file": "RM005_3_in_edges_median_template.rq",
+                        "replace_dict": {
+                            "{median_position}": "RM005_1_in_edges_total_template.rq"
+                        },
+                        "execute": true,
+                        "result": "-",
+                        "execution_time": 0.02
+                    },
+                    "max": {
+                        "name": "Maximum of incoming edges",
+                        "file": "RM005_4_in_edges_max_template.rq",
+                        "execute": true,
+                        "result": "-",
+                        "execution_time": 0.01
+                    }
+                }
+            },
+            "connectivity": {
+                "name": "Connectivity",
+                "file": "RM006_connectivity_template.rq",
+                "execute": true,
+                "result": "15",
+                "execution_time": 0.01
+            }
+        }
+    },
+    "learning_resource": {
+        "file": "08_learning_resource.md",
+        "uri": "<http://schema.org/LearningResource>",
+        "queries": {
+            "instances": {
+                "name": "Number of Resources",
+                "file": "RM001_instances_template.rq",
+                "execute": true,
+                "result": "512",
+                "execution_time": 0.02
+            },
+            "assertions": {
+                "name": "Number of Assertions",
+                "file": "RM002_assertions_template.rq",
+                "execute": true,
+                "result": "513",
+                "execution_time": 0.02
+            },
+            "linkage": {
+                "name": "Average Linkage",
+                "file": "RM003_linkage_template.rq",
+                "execute": true,
+                "result": "25",
+                "execution_time": 0.02
+            },
+            "edges_out": {
+                "name": "Edges - outgoing",
+                "files": {
+                    "total": {
+                        "name": "Total number of outgoing edges",
+                        "file": "RM004_1_out_edges_total_template.rq",
+                        "execute": true,
+                        "result": "512",
+                        "execution_time": 0.01
+                    },
+                    "min": {
+                        "name": "Minimum of outgoing edges",
+                        "file": "RM004_2_out_edges_min_template.rq",
+                        "execute": true,
+                        "result": "10",
+                        "execution_time": 0.02
+                    },
+                    "median": {
+                        "name": "Median of outgoing edges",
+                        "file": "RM004_3_out_edges_median_template.rq",
+                        "replace_dict": {
+                            "{median_position}": "RM004_1_out_edges_total_template.rq"
+                        },
+                        "execute": true,
+                        "result": "15",
+                        "execution_time": 0.02
+                    },
+                    "max": {
+                        "name": "Maximum of outgoing edges",
+                        "file": "RM004_4_out_edges_max_template.rq",
+                        "execute": true,
+                        "result": "27",
+                        "execution_time": 0.02
+                    }
+                }
+            },
+            "edges_in": {
+                "name": "Edges - incoming",
+                "files": {
+                    "total": {
+                        "name": "Total number of incoming edges",
+                        "file": "RM005_1_in_edges_total_template.rq",
+                        "execute": true,
+                        "result": "1",
+                        "execution_time": 0.01
+                    },
+                    "min": {
+                        "name": "Minimum of incoming edges",
+                        "file": "RM005_2_in_edges_min_template.rq",
+                        "execute": true,
+                        "result": "3",
+                        "execution_time": 0.02
+                    },
+                    "median": {
+                        "name": "Median of incoming edges",
+                        "file": "RM005_3_in_edges_median_template.rq",
+                        "replace_dict": {
+                            "{median_position}": "RM005_1_in_edges_total_template.rq"
+                        },
+                        "execute": true,
+                        "result": "3",
+                        "execution_time": 0.01
+                    },
+                    "max": {
+                        "name": "Maximum of incoming edges",
+                        "file": "RM005_4_in_edges_max_template.rq",
+                        "execute": true,
+                        "result": "3",
+                        "execution_time": 0.02
+                    }
+                }
+            },
+            "connectivity": {
+                "name": "Connectivity",
+                "file": "RM006_connectivity_template.rq",
+                "execute": true,
+                "result": "530",
+                "execution_time": 0.02
+            }
+        }
+    },
+    "repository": {
+        "file": "09_repository.md",
+        "uri": "<http://nfdi4earth.de/ontology/Repository>",
+        "queries": {
+            "instances": {
+                "name": "Number of Resources",
+                "file": "RM001_instances_template.rq",
+                "execute": true,
+                "result": "159",
+                "execution_time": 0.02
+            },
+            "assertions": {
+                "name": "Number of Assertions",
+                "file": "RM002_assertions_template.rq",
+                "execute": true,
+                "result": "162",
+                "execution_time": 0.01
+            },
+            "linkage": {
+                "name": "Average Linkage",
+                "file": "RM003_linkage_template.rq",
+                "execute": true,
+                "result": "90212",
+                "execution_time": 0.02
+            },
+            "edges_out": {
+                "name": "Edges - outgoing",
+                "files": {
+                    "total": {
+                        "name": "Total number of outgoing edges",
+                        "file": "RM004_1_out_edges_total_template.rq",
+                        "execute": true,
+                        "result": "159",
+                        "execution_time": 0.01
+                    },
+                    "min": {
+                        "name": "Minimum of outgoing edges",
+                        "file": "RM004_2_out_edges_min_template.rq",
+                        "execute": true,
+                        "result": "7",
+                        "execution_time": 0.02
+                    },
+                    "median": {
+                        "name": "Median of outgoing edges",
+                        "file": "RM004_3_out_edges_median_template.rq",
+                        "replace_dict": {
+                            "{median_position}": "RM004_1_out_edges_total_template.rq"
+                        },
+                        "execute": true,
+                        "result": "44",
+                        "execution_time": 0.02
+                    },
+                    "max": {
+                        "name": "Maximum of outgoing edges",
+                        "file": "RM004_4_out_edges_max_template.rq",
+                        "execute": true,
+                        "result": "92",
+                        "execution_time": 0.02
+                    }
+                }
+            },
+            "edges_in": {
+                "name": "Edges - incoming",
+                "files": {
+                    "total": {
+                        "name": "Total number of incoming edges",
+                        "file": "RM005_1_in_edges_total_template.rq",
+                        "execute": true,
+                        "result": "5",
+                        "execution_time": 0.06
+                    },
+                    "min": {
+                        "name": "Minimum of incoming edges",
+                        "file": "RM005_2_in_edges_min_template.rq",
+                        "execute": true,
+                        "result": "1417",
+                        "execution_time": 0.03
+                    },
+                    "median": {
+                        "name": "Median of incoming edges",
+                        "file": "RM005_3_in_edges_median_template.rq",
+                        "replace_dict": {
+                            "{median_position}": "RM005_1_in_edges_total_template.rq"
+                        },
+                        "execute": true,
+                        "result": "6718",
+                        "execution_time": 0.04
+                    },
+                    "max": {
+                        "name": "Maximum of incoming edges",
+                        "file": "RM005_4_in_edges_max_template.rq",
+                        "execute": true,
+                        "result": "432941",
+                        "execution_time": 0.03
+                    }
+                }
+            },
+            "connectivity": {
+                "name": "Connectivity",
+                "file": "RM006_connectivity_template.rq",
+                "execute": true,
+                "result": "825",
+                "execution_time": 0.02
+            }
+        }
+    },
+    "article_lhb": {
+        "file": "10_article_lhb.md",
+        "uri": "<http://nfdi4earth.de/ontology/LHBArticle>",
+        "queries": {
+            "instances": {
+                "name": "Number of Resources",
+                "file": "RM001_instances_template.rq",
+                "execute": true,
+                "result": "114",
+                "execution_time": 0.01
+            },
+            "assertions": {
+                "name": "Number of Assertions",
+                "file": "RM002_assertions_template.rq",
+                "execute": true,
+                "result": "117",
+                "execution_time": 0.01
+            },
+            "linkage": {
+                "name": "Average Linkage",
+                "file": "RM003_linkage_template.rq",
+                "execute": true,
+                "result": "33.245098039215686",
+                "execution_time": 0.02
+            },
+            "edges_out": {
+                "name": "Edges - outgoing",
+                "files": {
+                    "total": {
+                        "name": "Total number of outgoing edges",
+                        "file": "RM004_1_out_edges_total_template.rq",
+                        "execute": true,
+                        "result": "114",
+                        "execution_time": 1.04
+                    },
+                    "min": {
+                        "name": "Minimum of outgoing edges",
+                        "file": "RM004_2_out_edges_min_template.rq",
+                        "execute": true,
+                        "result": "11",
+                        "execution_time": 0.01
+                    },
+                    "median": {
+                        "name": "Median of outgoing edges",
+                        "file": "RM004_3_out_edges_median_template.rq",
+                        "replace_dict": {
+                            "{median_position}": "RM004_1_out_edges_total_template.rq"
+                        },
+                        "execute": true,
+                        "result": "30",
+                        "execution_time": 0.01
+                    },
+                    "max": {
+                        "name": "Maximum of outgoing edges",
+                        "file": "RM004_4_out_edges_max_template.rq",
+                        "execute": true,
+                        "result": "62",
+                        "execution_time": 0.01
+                    }
+                }
+            },
+            "edges_in": {
+                "name": "Edges - incoming",
+                "files": {
+                    "total": {
+                        "name": "Total number of incoming edges",
+                        "file": "RM005_1_in_edges_total_template.rq",
+                        "execute": true,
+                        "result": "102",
+                        "execution_time": 0.01
+                    },
+                    "min": {
+                        "name": "Minimum of incoming edges",
+                        "file": "RM005_2_in_edges_min_template.rq",
+                        "execute": true,
+                        "result": "1",
+                        "execution_time": 0.02
+                    },
+                    "median": {
+                        "name": "Median of incoming edges",
+                        "file": "RM005_3_in_edges_median_template.rq",
+                        "replace_dict": {
+                            "{median_position}": "RM005_1_in_edges_total_template.rq"
+                        },
+                        "execute": true,
+                        "result": "2",
+                        "execution_time": 1.06
+                    },
+                    "max": {
+                        "name": "Maximum of incoming edges",
+                        "file": "RM005_4_in_edges_max_template.rq",
+                        "execute": true,
+                        "result": "24",
+                        "execution_time": 0.01
+                    }
+                }
+            },
+            "connectivity": {
+                "name": "Connectivity",
+                "file": "RM006_connectivity_template.rq",
+                "execute": true,
+                "result": "592",
+                "execution_time": 0.01
+            }
+        }
+    },
+    "standards": {
+        "file": "11_standards.md",
+        "uri": "<http://nfdi4earth.de/ontology/MetadataStandard>",
+        "queries": {
+            "instances": {
+                "name": "Number of Resources",
+                "file": "RM001_instances_template.rq",
+                "execute": true,
+                "result": "89",
+                "execution_time": 1.07
+            },
+            "assertions": {
+                "name": "Number of Assertions",
+                "file": "RM002_assertions_template.rq",
+                "execute": true,
+                "result": "101",
+                "execution_time": 0.01
+            },
+            "linkage": {
+                "name": "Average Linkage",
+                "file": "RM003_linkage_template.rq",
+                "execute": true,
+                "result": "13.714285714285714",
+                "execution_time": 1.02
+            },
+            "edges_out": {
+                "name": "Edges - outgoing",
+                "files": {
+                    "total": {
+                        "name": "Total number of outgoing edges",
+                        "file": "RM004_1_out_edges_total_template.rq",
+                        "execute": true,
+                        "result": "89",
+                        "execution_time": 0.01
+                    },
+                    "min": {
+                        "name": "Minimum of outgoing edges",
+                        "file": "RM004_2_out_edges_min_template.rq",
+                        "execute": true,
+                        "result": "5",
+                        "execution_time": 0.02
+                    },
+                    "median": {
+                        "name": "Median of outgoing edges",
+                        "file": "RM004_3_out_edges_median_template.rq",
+                        "replace_dict": {
+                            "{median_position}": "RM004_1_out_edges_total_template.rq"
+                        },
+                        "execute": true,
+                        "result": "9",
+                        "execution_time": 0.01
+                    },
+                    "max": {
+                        "name": "Maximum of outgoing edges",
+                        "file": "RM004_4_out_edges_max_template.rq",
+                        "execute": true,
+                        "result": "27",
+                        "execution_time": 0.01
+                    }
+                }
+            },
+            "edges_in": {
+                "name": "Edges - incoming",
+                "files": {
+                    "total": {
+                        "name": "Total number of incoming edges",
+                        "file": "RM005_1_in_edges_total_template.rq",
+                        "execute": true,
+                        "result": "77",
+                        "execution_time": 0.01
+                    },
+                    "min": {
+                        "name": "Minimum of incoming edges",
+                        "file": "RM005_2_in_edges_min_template.rq",
+                        "execute": true,
+                        "result": "1",
+                        "execution_time": 0.01
+                    },
+                    "median": {
+                        "name": "Median of incoming edges",
+                        "file": "RM005_3_in_edges_median_template.rq",
+                        "replace_dict": {
+                            "{median_position}": "RM005_1_in_edges_total_template.rq"
+                        },
+                        "execute": true,
+                        "result": "1",
+                        "execution_time": 0.02
+                    },
+                    "max": {
+                        "name": "Maximum of incoming edges",
+                        "file": "RM005_4_in_edges_max_template.rq",
+                        "execute": true,
+                        "result": "56",
+                        "execution_time": 0.02
+                    }
+                }
+            },
+            "connectivity": {
+                "name": "Connectivity",
+                "file": "RM006_connectivity_template.rq",
+                "execute": true,
+                "result": "100",
+                "execution_time": 0.02
+            }
+        }
+    },
+    "software": {
+        "file": "13_software.md",
+        "uri": "<http://schema.org/SoftwareSourceCode>",
+        "queries": {
+            "instances": {
+                "name": "Number of Resources",
+                "file": "RM001_instances_template.rq",
+                "execute": true,
+                "result": "147",
+                "execution_time": 0.01
+            },
+            "assertions": {
+                "name": "Number of Assertions",
+                "file": "RM002_assertions_template.rq",
+                "execute": true,
+                "result": "148",
+                "execution_time": 0.01
+            },
+            "linkage": {
+                "name": "Average Linkage",
+                "file": "RM003_linkage_template.rq",
+                "execute": true,
+                "result": "11.5",
+                "execution_time": 0.04
+            },
+            "edges_out": {
+                "name": "Edges - outgoing",
+                "files": {
+                    "total": {
+                        "name": "Total number of outgoing edges",
+                        "file": "RM004_1_out_edges_total_template.rq",
+                        "execute": true,
+                        "result": "147",
+                        "execution_time": 0.01
+                    },
+                    "min": {
+                        "name": "Minimum of outgoing edges",
+                        "file": "RM004_2_out_edges_min_template.rq",
+                        "execute": true,
+                        "result": "7",
+                        "execution_time": 0.01
+                    },
+                    "median": {
+                        "name": "Median of outgoing edges",
+                        "file": "RM004_3_out_edges_median_template.rq",
+                        "replace_dict": {
+                            "{median_position}": "RM004_1_out_edges_total_template.rq"
+                        },
+                        "execute": true,
+                        "result": "20",
+                        "execution_time": 0.01
+                    },
+                    "max": {
+                        "name": "Maximum of outgoing edges",
+                        "file": "RM004_4_out_edges_max_template.rq",
+                        "execute": true,
+                        "result": "122",
+                        "execution_time": 0.01
+                    }
+                }
+            },
+            "edges_in": {
+                "name": "Edges - incoming",
+                "files": {
+                    "total": {
+                        "name": "Total number of incoming edges",
+                        "file": "RM005_1_in_edges_total_template.rq",
+                        "execute": true,
+                        "result": "8",
+                        "execution_time": 0.01
+                    },
+                    "min": {
+                        "name": "Minimum of incoming edges",
+                        "file": "RM005_2_in_edges_min_template.rq",
+                        "execute": true,
+                        "result": "1",
+                        "execution_time": 0.01
+                    },
+                    "median": {
+                        "name": "Median of incoming edges",
+                        "file": "RM005_3_in_edges_median_template.rq",
+                        "replace_dict": {
+                            "{median_position}": "RM005_1_in_edges_total_template.rq"
+                        },
+                        "execute": true,
+                        "result": "1",
+                        "execution_time": 0.02
+                    },
+                    "max": {
+                        "name": "Maximum of incoming edges",
+                        "file": "RM005_4_in_edges_max_template.rq",
+                        "execute": true,
+                        "result": "2",
+                        "execution_time": 0.02
+                    }
+                }
+            },
+            "connectivity": {
+                "name": "Connectivity",
+                "file": "RM006_connectivity_template.rq",
+                "execute": true,
+                "result": "1103",
+                "execution_time": 0.02
+            }
+        }
+    },
+    "service": {
+        "file": "14_service.md",
+        "uri": "<http://www.w3.org/ns/sparql-service-description#Service>",
+        "queries": {
+            "instances": {
+                "name": "Number of Resources",
+                "file": "RM001_instances_template.rq",
+                "execute": true,
+                "result": "1",
+                "execution_time": 0.02
+            },
+            "assertions": {
+                "name": "Number of Assertions",
+                "file": "RM002_assertions_template.rq",
+                "execute": true,
+                "result": "1",
+                "execution_time": 1.06
+            },
+            "linkage": {
+                "name": "Average Linkage",
+                "file": "RM003_linkage_template.rq",
+                "execute": true,
+                "result": 0,
+                "execution_time": 0.02
+            },
+            "edges_out": {
+                "name": "Edges - outgoing",
+                "files": {
+                    "total": {
+                        "name": "Total number of outgoing edges",
+                        "file": "RM004_1_out_edges_total_template.rq",
+                        "execute": true,
+                        "result": "1",
+                        "execution_time": 0.02
+                    },
+                    "min": {
+                        "name": "Minimum of outgoing edges",
+                        "file": "RM004_2_out_edges_min_template.rq",
+                        "execute": true,
+                        "result": "14",
+                        "execution_time": 0.02
+                    },
+                    "median": {
+                        "name": "Median of outgoing edges",
+                        "file": "RM004_3_out_edges_median_template.rq",
+                        "replace_dict": {
+                            "{median_position}": "RM004_1_out_edges_total_template.rq"
+                        },
+                        "execute": true,
+                        "result": "14",
+                        "execution_time": 0.01
+                    },
+                    "max": {
+                        "name": "Maximum of outgoing edges",
+                        "file": "RM004_4_out_edges_max_template.rq",
+                        "execute": true,
+                        "result": "14",
+                        "execution_time": 0.01
+                    }
+                }
+            },
+            "edges_in": {
+                "name": "Edges - incoming",
+                "files": {
+                    "total": {
+                        "name": "Total number of incoming edges",
+                        "file": "RM005_1_in_edges_total_template.rq",
+                        "execute": true,
+                        "result": "0",
+                        "execution_time": 0.01
+                    },
+                    "min": {
+                        "name": "Minimum of incoming edges",
+                        "file": "RM005_2_in_edges_min_template.rq",
+                        "execute": true,
+                        "result": "-",
+                        "execution_time": 0.02
+                    },
+                    "median": {
+                        "name": "Median of incoming edges",
+                        "file": "RM005_3_in_edges_median_template.rq",
+                        "replace_dict": {
+                            "{median_position}": "RM005_1_in_edges_total_template.rq"
+                        },
+                        "execute": true,
+                        "result": "-",
+                        "execution_time": 0.02
+                    },
+                    "max": {
+                        "name": "Maximum of incoming edges",
+                        "file": "RM005_4_in_edges_max_template.rq",
+                        "execute": true,
+                        "result": "-",
+                        "execution_time": 1.05
+                    }
+                }
+            },
+            "connectivity": {
+                "name": "Connectivity",
+                "file": "RM006_connectivity_template.rq",
+                "execute": true,
+                "result": "13",
+                "execution_time": 0.01
+            }
+        }
+    },
+    "data_service": {
+        "file": "15_data_service.md",
+        "uri": "<http://www.w3.org/ns/dcat#DataService>",
+        "queries": {
+            "instances": {
+                "name": "Number of Resources",
+                "file": "RM001_instances_template.rq",
+                "execute": true,
+                "result": "363632",
+                "execution_time": 0.16
+            },
+            "assertions": {
+                "name": "Number of Assertions",
+                "file": "RM002_assertions_template.rq",
+                "execute": true,
+                "result": "363633",
+                "execution_time": 0.02
+            },
+            "linkage": {
+                "name": "Average Linkage",
+                "file": "RM003_linkage_template.rq",
+                "execute": true,
+                "result": 0,
+                "execution_time": 0.04
+            },
+            "edges_out": {
+                "name": "Edges - outgoing",
+                "files": {
+                    "total": {
+                        "name": "Total number of outgoing edges",
+                        "file": "RM004_1_out_edges_total_template.rq",
+                        "execute": true,
+                        "result": "363632",
+                        "execution_time": 1.71
+                    },
+                    "min": {
+                        "name": "Minimum of outgoing edges",
+                        "file": "RM004_2_out_edges_min_template.rq",
+                        "execute": true,
+                        "result": "11",
+                        "execution_time": 0.4
+                    },
+                    "median": {
+                        "name": "Median of outgoing edges",
+                        "file": "RM004_3_out_edges_median_template.rq",
+                        "replace_dict": {
+                            "{median_position}": "RM004_1_out_edges_total_template.rq"
+                        },
+                        "execute": true,
+                        "result": "28",
+                        "execution_time": 0.69
+                    },
+                    "max": {
+                        "name": "Maximum of outgoing edges",
+                        "file": "RM004_4_out_edges_max_template.rq",
+                        "execute": true,
+                        "result": "12519",
+                        "execution_time": 0.39
+                    }
+                }
+            },
+            "edges_in": {
+                "name": "Edges - incoming",
+                "files": {
+                    "total": {
+                        "name": "Total number of incoming edges",
+                        "file": "RM005_1_in_edges_total_template.rq",
+                        "execute": true,
+                        "result": "0",
+                        "execution_time": 0.07
+                    },
+                    "min": {
+                        "name": "Minimum of incoming edges",
+                        "file": "RM005_2_in_edges_min_template.rq",
+                        "execute": true,
+                        "result": "-",
+                        "execution_time": 0.03
+                    },
+                    "median": {
+                        "name": "Median of incoming edges",
+                        "file": "RM005_3_in_edges_median_template.rq",
+                        "replace_dict": {
+                            "{median_position}": "RM005_1_in_edges_total_template.rq"
+                        },
+                        "execute": true,
+                        "result": "-",
+                        "execution_time": 0.03
+                    },
+                    "max": {
+                        "name": "Maximum of incoming edges",
+                        "file": "RM005_4_in_edges_max_template.rq",
+                        "execute": true,
+                        "result": "-",
+                        "execution_time": 0.03
+                    }
+                }
+            },
+            "connectivity": {
+                "name": "Connectivity",
+                "file": "RM006_connectivity_template.rq",
+                "execute": true,
+                "result": "58909",
+                "execution_time": 2.87
+            }
+        }
+    },
+    "aggregator": {
+        "file": "16_aggregator.md",
+        "uri": "<http://nfdi4earth.de/ontology/Aggregator>",
+        "queries": {
+            "instances": {
+                "name": "Number of Resources",
+                "file": "RM001_instances_template.rq",
+                "execute": true,
+                "result": "38",
+                "execution_time": 0.01
+            },
+            "assertions": {
+                "name": "Number of Assertions",
+                "file": "RM002_assertions_template.rq",
+                "execute": true,
+                "result": "39",
+                "execution_time": 0.01
+            },
+            "linkage": {
+                "name": "Average Linkage",
+                "file": "RM003_linkage_template.rq",
+                "execute": true,
+                "result": "187",
+                "execution_time": 0.02
+            },
+            "edges_out": {
+                "name": "Edges - outgoing",
+                "files": {
+                    "total": {
+                        "name": "Total number of outgoing edges",
+                        "file": "RM004_1_out_edges_total_template.rq",
+                        "execute": true,
+                        "result": "38",
+                        "execution_time": 0.01
+                    },
+                    "min": {
+                        "name": "Minimum of outgoing edges",
+                        "file": "RM004_2_out_edges_min_template.rq",
+                        "execute": true,
+                        "result": "8",
+                        "execution_time": 0.01
+                    },
+                    "median": {
+                        "name": "Median of outgoing edges",
+                        "file": "RM004_3_out_edges_median_template.rq",
+                        "replace_dict": {
+                            "{median_position}": "RM004_1_out_edges_total_template.rq"
+                        },
+                        "execute": true,
+                        "result": "36",
+                        "execution_time": 0.01
+                    },
+                    "max": {
+                        "name": "Maximum of outgoing edges",
+                        "file": "RM004_4_out_edges_max_template.rq",
+                        "execute": true,
+                        "result": "57",
+                        "execution_time": 0.01
+                    }
+                }
+            },
+            "edges_in": {
+                "name": "Edges - incoming",
+                "files": {
+                    "total": {
+                        "name": "Total number of incoming edges",
+                        "file": "RM005_1_in_edges_total_template.rq",
+                        "execute": true,
+                        "result": "1",
+                        "execution_time": 0.01
+                    },
+                    "min": {
+                        "name": "Minimum of incoming edges",
+                        "file": "RM005_2_in_edges_min_template.rq",
+                        "execute": true,
+                        "result": "151",
+                        "execution_time": 1.04
+                    },
+                    "median": {
+                        "name": "Median of incoming edges",
+                        "file": "RM005_3_in_edges_median_template.rq",
+                        "replace_dict": {
+                            "{median_position}": "RM005_1_in_edges_total_template.rq"
+                        },
+                        "execute": true,
+                        "result": "151",
+                        "execution_time": 1.01
+                    },
+                    "max": {
+                        "name": "Maximum of incoming edges",
+                        "file": "RM005_4_in_edges_max_template.rq",
+                        "execute": true,
+                        "result": "151",
+                        "execution_time": 0.01
+                    }
+                }
+            },
+            "connectivity": {
+                "name": "Connectivity",
+                "file": "RM006_connectivity_template.rq",
+                "execute": true,
+                "result": "239",
+                "execution_time": 0.01
+            }
+        }
+    },
+    "person": {
+        "file": "17_person.md",
+        "uri": "<http://schema.org/Person>",
+        "queries": {
+            "instances": {
+                "name": "Number of Resources",
+                "file": "RM001_instances_template.rq",
+                "execute": true,
+                "result": "2180",
+                "execution_time": 0.01
+            },
+            "assertions": {
+                "name": "Number of Assertions",
+                "file": "RM002_assertions_template.rq",
+                "execute": true,
+                "result": "2181",
+                "execution_time": 0.01
+            },
+            "linkage": {
+                "name": "Average Linkage",
+                "file": "RM003_linkage_template.rq",
+                "execute": true,
+                "result": "4.628269848554383",
+                "execution_time": 0.02
+            },
+            "edges_out": {
+                "name": "Edges - outgoing",
+                "files": {
+                    "total": {
+                        "name": "Total number of outgoing edges",
+                        "file": "RM004_1_out_edges_total_template.rq",
+                        "execute": true,
+                        "result": "2180",
+                        "execution_time": 0.01
+                    },
+                    "min": {
+                        "name": "Minimum of outgoing edges",
+                        "file": "RM004_2_out_edges_min_template.rq",
+                        "execute": true,
+                        "result": "2",
+                        "execution_time": 0.01
+                    },
+                    "median": {
+                        "name": "Median of outgoing edges",
+                        "file": "RM004_3_out_edges_median_template.rq",
+                        "replace_dict": {
+                            "{median_position}": "RM004_1_out_edges_total_template.rq"
+                        },
+                        "execute": true,
+                        "result": "4",
+                        "execution_time": 1.05
+                    },
+                    "max": {
+                        "name": "Maximum of outgoing edges",
+                        "file": "RM004_4_out_edges_max_template.rq",
+                        "execute": true,
+                        "result": "7",
+                        "execution_time": 0.01
+                    }
+                }
+            },
+            "edges_in": {
+                "name": "Edges - incoming",
+                "files": {
+                    "total": {
+                        "name": "Total number of incoming edges",
+                        "file": "RM005_1_in_edges_total_template.rq",
+                        "execute": true,
+                        "result": "2179",
+                        "execution_time": 0.01
+                    },
+                    "min": {
+                        "name": "Minimum of incoming edges",
+                        "file": "RM005_2_in_edges_min_template.rq",
+                        "execute": true,
+                        "result": "1",
+                        "execution_time": 0.01
+                    },
+                    "median": {
+                        "name": "Median of incoming edges",
+                        "file": "RM005_3_in_edges_median_template.rq",
+                        "replace_dict": {
+                            "{median_position}": "RM005_1_in_edges_total_template.rq"
+                        },
+                        "execute": true,
+                        "result": "1",
+                        "execution_time": 0.02
+                    },
+                    "max": {
+                        "name": "Maximum of incoming edges",
+                        "file": "RM005_4_in_edges_max_template.rq",
+                        "execute": true,
+                        "result": "2",
+                        "execution_time": 0.01
+                    }
+                }
+            },
+            "connectivity": {
+                "name": "Connectivity",
+                "file": "RM006_connectivity_template.rq",
+                "execute": true,
+                "result": "2",
+                "execution_time": 0.01
+            }
+        }
+    },
+    "registry": {
+        "file": "18_registry.md",
+        "uri": "<http://nfdi4earth.de/ontology/Registry>",
+        "queries": {
+            "instances": {
+                "name": "Number of Resources",
+                "file": "RM001_instances_template.rq",
+                "execute": true,
+                "result": "5",
+                "execution_time": 0.01
+            },
+            "assertions": {
+                "name": "Number of Assertions",
+                "file": "RM002_assertions_template.rq",
+                "execute": true,
+                "result": "6",
+                "execution_time": 0.01
+            },
+            "linkage": {
+                "name": "Average Linkage",
+                "file": "RM003_linkage_template.rq",
+                "execute": true,
+                "result": 0,
+                "execution_time": 0.01
+            },
+            "edges_out": {
+                "name": "Edges - outgoing",
+                "files": {
+                    "total": {
+                        "name": "Total number of outgoing edges",
+                        "file": "RM004_1_out_edges_total_template.rq",
+                        "execute": true,
+                        "result": "5",
+                        "execution_time": 0.01
+                    },
+                    "min": {
+                        "name": "Minimum of outgoing edges",
+                        "file": "RM004_2_out_edges_min_template.rq",
+                        "execute": true,
+                        "result": "7",
+                        "execution_time": 0.01
+                    },
+                    "median": {
+                        "name": "Median of outgoing edges",
+                        "file": "RM004_3_out_edges_median_template.rq",
+                        "replace_dict": {
+                            "{median_position}": "RM004_1_out_edges_total_template.rq"
+                        },
+                        "execute": true,
+                        "result": "9",
+                        "execution_time": 0.01
+                    },
+                    "max": {
+                        "name": "Maximum of outgoing edges",
+                        "file": "RM004_4_out_edges_max_template.rq",
+                        "execute": true,
+                        "result": "11",
+                        "execution_time": 0.01
+                    }
+                }
+            },
+            "edges_in": {
+                "name": "Edges - incoming",
+                "files": {
+                    "total": {
+                        "name": "Total number of incoming edges",
+                        "file": "RM005_1_in_edges_total_template.rq",
+                        "execute": true,
+                        "result": "0",
+                        "execution_time": 0.01
+                    },
+                    "min": {
+                        "name": "Minimum of incoming edges",
+                        "file": "RM005_2_in_edges_min_template.rq",
+                        "execute": true,
+                        "result": "-",
+                        "execution_time": 0.01
+                    },
+                    "median": {
+                        "name": "Median of incoming edges",
+                        "file": "RM005_3_in_edges_median_template.rq",
+                        "replace_dict": {
+                            "{median_position}": "RM005_1_in_edges_total_template.rq"
+                        },
+                        "execute": true,
+                        "result": "-",
+                        "execution_time": 0.01
+                    },
+                    "max": {
+                        "name": "Maximum of incoming edges",
+                        "file": "RM005_4_in_edges_max_template.rq",
+                        "execute": true,
+                        "result": "-",
+                        "execution_time": 0.01
+                    }
+                }
+            },
+            "connectivity": {
+                "name": "Connectivity",
+                "file": "RM006_connectivity_template.rq",
+                "execute": true,
+                "result": "15",
+                "execution_time": 0.01
+            }
+        }
+    },
+    "timestamp": "2025-03-14T16:21"
+}
\ No newline at end of file
diff --git a/scripts/kg_analysis/metrics_runner.py b/scripts/kg_analysis/metrics_runner.py
index 206192d..82190c6 100644
--- a/scripts/kg_analysis/metrics_runner.py
+++ b/scripts/kg_analysis/metrics_runner.py
@@ -3,13 +3,13 @@
 # ralf.klammer@tu-dresden.de
 import logging
 
-from time import time
+import json
 
 from abc import ABC, abstractmethod
 from copy import deepcopy
 from datetime import datetime
 from pathlib import Path
-
+from time import time
 from typing import Optional
 
 from . import rprint
@@ -43,10 +43,22 @@ class MetricsRunnerBase(ABC):
     def query_path(self):
         return self.get_query_path(self.query_file)
 
+    def get_output_path(self, suffix: str = ".txt"):
+        filename = self._output_file or self.query_file.with_suffix(suffix)
+        return self.base_output_path.joinpath(filename)
+
     @property
     def output_path(self):
-        filename = self._output_file or self.query_file.with_suffix(".txt")
-        return self.base_output_path.joinpath(filename)
+        log.warning("Deprecated: Use output_path_txt or output_path_json")
+        return self.get_output_path()
+
+    @property
+    def output_path_txt(self):
+        return self.get_output_path(suffix=".txt")
+
+    @property
+    def output_path_json(self):
+        return self.get_output_path(suffix=".json")
 
     def query_metric(self, query_path: Path, **kwargs) -> Optional[int]:
         """Run a metric query and the result"""
@@ -69,20 +81,38 @@ class MetricsRunnerBase(ABC):
         """Run this specific metric."""
         pass
 
-    def save(self, result) -> None:
+    def save_report(self, result) -> None:
         """Run a metric query and optionally save the results."""
 
         if self.fail:
             log.error(f"Failed to run metric: {self.__class__.__name__}")
 
-        with open(self.output_path, "w") as f:
+        with open(self.output_path_txt, "w") as f:
             f.write(f"{datetime.now().isoformat(timespec="minutes")}\n")
             f.write(str(result))
             if self._execution_time:
                 f.write(
                     f"\n- Execution time: {self._execution_time:.2f} seconds"
                 )
-            log.info(f"Results saved to {self.output_path}")
+            log.info(f"Results saved to {self.output_path_txt}")
+
+    def save(self, result) -> None:
+        log.warning("Deprecated: Use save_report")
+        return self.save_report(result)
+
+    def save_to_json(self, dictionary, filename: Optional[str] = None):
+        """
+        Saves a dictionary to a JSON file
+
+        Args:
+            dictionary (dict): The dictionary to save
+            filename (str): Name of the output file
+            (default: self.output_path_json)
+        """
+        with open(
+            filename or self.output_path_json, "w", encoding="utf-8"
+        ) as f:
+            json.dump(dictionary, f, indent=4, ensure_ascii=False)
 
 
 class MetricsRunner_001(MetricsRunnerBase):
@@ -187,7 +217,7 @@ class MetricsRunner_Resources(MetricsRunnerBase):
         "assertions": {
             "name": "Number of Assertions",
             "file": "RM002_assertions_template.rq",
-            "execute": False,
+            "execute": True,
         },
         "linkage": {
             "name": "Average Linkage",
@@ -210,7 +240,10 @@ class MetricsRunner_Resources(MetricsRunnerBase):
                 "median": {
                     "name": "Median of outgoing edges",
                     "file": "RM004_3_out_edges_median_template.rq",
-                    "execute": False,
+                    "replace_dict": {
+                        "{median_position}": "RM004_1_out_edges_total_template.rq"
+                    },
+                    "execute": True,
                 },
                 "max": {
                     "name": "Maximum of outgoing edges",
@@ -235,7 +268,10 @@ class MetricsRunner_Resources(MetricsRunnerBase):
                 "median": {
                     "name": "Median of incoming edges",
                     "file": "RM005_3_in_edges_median_template.rq",
-                    "execute": False,
+                    "replace_dict": {
+                        "{median_position}": "RM005_1_in_edges_total_template.rq"
+                    },
+                    "execute": True,
                 },
                 "max": {
                     "name": "Maximum of incoming edges",
@@ -306,15 +342,18 @@ class MetricsRunner_Resources(MetricsRunnerBase):
         },
     }
 
-    def _get_result(self, query, resource_type):
+    def _get_result(self, query, resource_type, replace_dict=None):
         log.info(f"Metric: {query['name']} ({query['file']})")
         if query["execute"] is False:
             return
+        _replace_dict = {
+            "{resource_type}": resource_type,
+        }
+        if replace_dict:
+            _replace_dict.update(replace_dict)
         result = self.query_metric(
             self.get_query_path(query["file"]),
-            replace_dict={
-                "{resource_type}": resource_type,
-            },
+            replace_dict=_replace_dict,
         )
         query["result"] = result
         query["execution_time"] = round(self._execution_time, 2)
@@ -325,12 +364,104 @@ class MetricsRunner_Resources(MetricsRunnerBase):
             queries = deepcopy(self.query_templates)
             for query in queries.values():
                 if "files" in query:
-                    for sub_query in query["files"].values():
-                        self._get_result(sub_query, data["uri"])
+                    for metric, sub_query in query["files"].items():
+                        replace_dict = {}
+                        if "replace_dict" in sub_query:
+                            for (
+                                dict_element,
+                                sub_sub_query,
+                            ) in sub_query["replace_dict"].items():
+                                replace_dict[dict_element] = self.query_metric(
+                                    self.get_query_path(sub_sub_query),
+                                    replace_dict={
+                                        "{resource_type}": data["uri"],
+                                    },
+                                )
+                                if (
+                                    replace_dict[dict_element]
+                                    and metric == "median"
+                                ):
+                                    replace_dict[dict_element] = (
+                                        int(replace_dict[dict_element]) / 2
+                                    )
+                        self._get_result(
+                            sub_query, data["uri"], replace_dict=replace_dict
+                        )
                 else:
                     self._get_result(query, data["uri"])
             self.resource_types[resource_type]["queries"] = queries
-        rprint(self.resource_types)
+        self.resource_types["timestamp"] = datetime.now().isoformat(
+            timespec="minutes"
+        )
+        self.save_to_json(
+            self.resource_types, "reports/metrics/resources.json"
+        )
+
+    # def read_resources(filename="reports/metrics/resources.json"):
+    #     """
+    #     Reads the resource_types from the JSON file
+
+    #     Args:
+    #         filename (str): Path to the JSON file
+    #     Returns:
+    #         dict: Dictionary with the resource types
+    #     """
+    #     filename = "reports/metrics/resources.json"
+    #     try:
+    #         with open(filename, "r", encoding="utf-8") as f:
+    #             return json.load(f)
+    #     except FileNotFoundError:
+    #         print(f"Error: The file {filename} was not found.")
+    #         return None
+    #     except json.JSONDecodeError:
+    #         print(f"Error: The file {filename} does not contain valid JSON.")
+    #         return None
+
+    # def export_report_to_table(self):
+    #     resources = self.read_resources()
+    #     table_header = "| Resource Type | Metric | Result |\n|--------------|--------|--------|\n"
+
+    #     for resource_type, resource_data in resources.items():
+    #         if resource_type == "timestamp":
+    #             continue
+
+    #         table_content = ""
+    #         for query_data in resource_data["queries"].values():
+    #             if "result" in query_data:
+    #                 table_content += f"| {resource_type} | {query_data['name']} | {query_data['result']} |\n"
+    #             else:
+    #                 table_content += (
+    #                     f"| {resource_type} | **{query_data['name']}** | |\n"
+    #                 )
+    #                 for sub_query_data in query_data["files"].values():
+    #                     table_content += f"| {resource_type} | {sub_query_data['name']} | {sub_query_data['result']} |\n"
+
+    #         self._query_file = resource_data["file"]
+    #         report = f"## Metrics for {resource_type}\n\n{table_header}{table_content}"
+    #         self.save_report(report)
+
+    # def export_reports(self):
+    #     resources = self.read_resources()
+    #     for resource_type, resource_data in resources.items():
+    #         if resource_type == "timestamp":
+    #             continue
+    #         # print(resource_type)
+    #         rprint(resource_data)
+    #         report = ""
+    #         for query_data in resource_data["queries"].values():
+    #             if "result" in query_data:
+    #                 report += (
+    #                     f"\n- {query_data['name']}: {query_data['result']}"
+    #                 )
+    #             else:
+    #                 report += f"\n- {query_data['name']}:"
+    #                 for sub_query_data in query_data["files"].values():
+    #                     report += f"\n    - {sub_query_data['name']}: {sub_query_data['result']}"
+    #         self._query_file = resource_data["file"]
+    #         self.save_report(report)
+    #         # print(resource_data["file"])
+    #     # print(self.output_path)
+    #     # self.save_report(report)
 
 
 class MetricsRunner(ABC):
@@ -341,4 +472,7 @@ class MetricsRunner(ABC):
         # MetricsRunner_003().run()
         # MetricsRunner_Edges("outgoing").run()
         # MetricsRunner_Edges("incoming").run()
-        MetricsRunner_Resources().run()
+        mr = MetricsRunner_Resources()
+        mr.run()
+        # mr.export_report_to_table()
+        # rprint(mr.resource_types)
-- 
GitLab


From a8e16cb98abb122a811774f4a654f410a0c7e8bd Mon Sep 17 00:00:00 2001
From: Ralf Klammer <ralf.klammer@tu-dresden.de>
Date: Fri, 14 Mar 2025 16:26:18 +0100
Subject: [PATCH 17/59] [metrics]fix: queries of min/max incoming edges

---
 queries/metrics/RM005_1_in_edges_total_template.rq  | 2 +-
 queries/metrics/RM005_2_in_edges_min_template.rq    | 4 ++--
 queries/metrics/RM005_3_in_edges_median_template.rq | 2 +-
 queries/metrics/RM005_4_in_edges_max_template.rq    | 4 ++--
 4 files changed, 6 insertions(+), 6 deletions(-)

diff --git a/queries/metrics/RM005_1_in_edges_total_template.rq b/queries/metrics/RM005_1_in_edges_total_template.rq
index 285ce77..3a04008 100644
--- a/queries/metrics/RM005_1_in_edges_total_template.rq
+++ b/queries/metrics/RM005_1_in_edges_total_template.rq
@@ -1,4 +1,4 @@
-# Returns a single number representing the cleaned count of unique incoming nodes for {resource_type} only.
+# Returns a single number representing the cleaned count of unique incoming edges for {resource_type} only.
 
 SELECT (COUNT(DISTINCT ?target) as ?uniqueEdges)
 WHERE {
diff --git a/queries/metrics/RM005_2_in_edges_min_template.rq b/queries/metrics/RM005_2_in_edges_min_template.rq
index c03d47a..afef730 100644
--- a/queries/metrics/RM005_2_in_edges_min_template.rq
+++ b/queries/metrics/RM005_2_in_edges_min_template.rq
@@ -1,6 +1,6 @@
-# This SPARQL query calculates the minimum out-edge for {resource_type} only.
+# This SPARQL query calculates the minimum incoming-edge for {resource_type} only.
 
-SELECT ?target (COUNT(?incoming) as ?inEdges)
+SELECT (COUNT(?incoming) as ?inEdges)
 WHERE {
 ?target a {resource_type} .
 ?incoming ?p ?target .
diff --git a/queries/metrics/RM005_3_in_edges_median_template.rq b/queries/metrics/RM005_3_in_edges_median_template.rq
index fa2b9c9..3d0a28e 100644
--- a/queries/metrics/RM005_3_in_edges_median_template.rq
+++ b/queries/metrics/RM005_3_in_edges_median_template.rq
@@ -1,4 +1,4 @@
-# This SPARQL query calculates the median in-edge for {resource_type} only.
+# This SPARQL query calculates the median incoming-edge for {resource_type} only.
 # The placeholder {median_position} must be replaced with the actual position of the median.
 
 SELECT ?inEdges as ?inMedian
diff --git a/queries/metrics/RM005_4_in_edges_max_template.rq b/queries/metrics/RM005_4_in_edges_max_template.rq
index 69dc4d6..0b10af8 100644
--- a/queries/metrics/RM005_4_in_edges_max_template.rq
+++ b/queries/metrics/RM005_4_in_edges_max_template.rq
@@ -1,6 +1,6 @@
-# This SPARQL query calculates the maximum out-edge for {resource_type} only.
+# This SPARQL query calculates the maximum incoming-edge for {resource_type} only.
 
-SELECT ?target (COUNT(?incoming) as ?inEdges)
+SELECT (COUNT(?incoming) as ?inEdges)
 WHERE {
 ?target a {resource_type} .
 ?incoming ?p ?target .
-- 
GitLab


From 81fec78c7112847122d38cd7e3afa6e524f69bb0 Mon Sep 17 00:00:00 2001
From: Ralf Klammer <ralf.klammer@tu-dresden.de>
Date: Fri, 14 Mar 2025 16:27:34 +0100
Subject: [PATCH 18/59] [metrics] MetricsRunner_Resources/run add comments and
 cleanup

---
 scripts/kg_analysis/metrics_runner.py | 167 ++++++++++----------------
 1 file changed, 65 insertions(+), 102 deletions(-)

diff --git a/scripts/kg_analysis/metrics_runner.py b/scripts/kg_analysis/metrics_runner.py
index 82190c6..65b0d35 100644
--- a/scripts/kg_analysis/metrics_runner.py
+++ b/scripts/kg_analysis/metrics_runner.py
@@ -358,110 +358,73 @@ class MetricsRunner_Resources(MetricsRunnerBase):
         query["result"] = result
         query["execution_time"] = round(self._execution_time, 2)
 
-    def run(self):
-        for resource_type, data in self.resource_types.items():
-            log.info(f"Analyzing: {data["uri"]}")
-            queries = deepcopy(self.query_templates)
-            for query in queries.values():
-                if "files" in query:
-                    for metric, sub_query in query["files"].items():
-                        replace_dict = {}
-                        if "replace_dict" in sub_query:
-                            for (
-                                dict_element,
-                                sub_sub_query,
-                            ) in sub_query["replace_dict"].items():
-                                replace_dict[dict_element] = self.query_metric(
-                                    self.get_query_path(sub_sub_query),
-                                    replace_dict={
-                                        "{resource_type}": data["uri"],
-                                    },
+
+def run(self):
+    """
+    Execute metrics analysis for all resource types and save results to JSON.
+    This method:
+    1. Iterates through all resource types
+    2. Executes queries for each metric
+    3. Handles complex metrics with sub-queries
+    4. Saves results to a JSON file
+    """
+    # Iterate through each resource type (dataset, publication, etc.)
+    for resource_type, data in self.resource_types.items():
+        log.info(f"Analyzing: {data['uri']}")
+        # Create a deep copy of query templates to avoid modifying the original
+        queries = deepcopy(self.query_templates)
+
+        # Process each query for the current resource type
+        for query in queries.values():
+            # Check if this is a composite query with sub-queries
+            if "files" in query:
+                # Process each sub-query (e.g., total, min, median, max)
+                for metric, sub_query in query["files"].items():
+                    # Initialize dictionary for replacement values
+                    replace_dict = {}
+
+                    # Handle queries that need pre-calculated values
+                    if "replace_dict" in sub_query:
+                        # Process each replacement needed for this query
+                        for dict_element, sub_sub_query in sub_query[
+                            "replace_dict"
+                        ].items():
+                            # Execute the sub-query to get the replacement value
+                            replace_dict[dict_element] = self.query_metric(
+                                self.get_query_path(sub_sub_query),
+                                replace_dict={
+                                    "{resource_type}": data["uri"],
+                                },
+                            )
+
+                            # Special handling for median calculations
+                            # If this is a median metric, divide the result by 2
+                            if (
+                                replace_dict[dict_element]
+                                and metric == "median"
+                            ):
+                                replace_dict[dict_element] = (
+                                    int(replace_dict[dict_element]) / 2
                                 )
-                                if (
-                                    replace_dict[dict_element]
-                                    and metric == "median"
-                                ):
-                                    replace_dict[dict_element] = (
-                                        int(replace_dict[dict_element]) / 2
-                                    )
-                        self._get_result(
-                            sub_query, data["uri"], replace_dict=replace_dict
-                        )
-                else:
-                    self._get_result(query, data["uri"])
-            self.resource_types[resource_type]["queries"] = queries
-        self.resource_types["timestamp"] = datetime.now().isoformat(
-            timespec="minutes"
-        )
-        self.save_to_json(
-            self.resource_types, "reports/metrics/resources.json"
-        )
 
-    # def read_resources(filename="reports/metrics/resources.json"):
-    #     """
-    #     Reads the resource_types from the JSON file
-
-    #     Args:
-    #         filename (str): Path to the JSON file
-    #     Returns:
-    #         dict: Dictionary with the resource types
-    #     """
-    #     filename = "reports/metrics/resources.json"
-    #     try:
-    #         with open(filename, "r", encoding="utf-8") as f:
-    #             return json.load(f)
-    #     except FileNotFoundError:
-    #         print(f"Error: The file {filename} was not found.")
-    #         return None
-    #     except json.JSONDecodeError:
-    #         print(f"Error: The file {filename} does not contain valid JSON.")
-    #         return None
-
-    # def export_report_to_table(self):
-    #     resources = self.read_resources()
-    #     table_header = "| Resource Type | Metric | Result |\n|--------------|--------|--------|\n"
-
-    #     for resource_type, resource_data in resources.items():
-    #         if resource_type == "timestamp":
-    #             continue
-
-    #         table_content = ""
-    #         for query_data in resource_data["queries"].values():
-    #             if "result" in query_data:
-    #                 table_content += f"| {resource_type} | {query_data['name']} | {query_data['result']} |\n"
-    #             else:
-    #                 table_content += (
-    #                     f"| {resource_type} | **{query_data['name']}** | |\n"
-    #                 )
-    #                 for sub_query_data in query_data["files"].values():
-    #                     table_content += f"| {resource_type} | {sub_query_data['name']} | {sub_query_data['result']} |\n"
-
-    #         self._query_file = resource_data["file"]
-    #         report = f"## Metrics for {resource_type}\n\n{table_header}{table_content}"
-    #         self.save_report(report)
-
-    # def export_reports(self):
-    #     resources = self.read_resources()
-    #     for resource_type, resource_data in resources.items():
-    #         if resource_type == "timestamp":
-    #             continue
-    #         # print(resource_type)
-    #         rprint(resource_data)
-    #         report = ""
-    #         for query_data in resource_data["queries"].values():
-    #             if "result" in query_data:
-    #                 report += (
-    #                     f"\n- {query_data['name']}: {query_data['result']}"
-    #                 )
-    #             else:
-    #                 report += f"\n- {query_data['name']}:"
-    #                 for sub_query_data in query_data["files"].values():
-    #                     report += f"\n    - {sub_query_data['name']}: {sub_query_data['result']}"
-    #         self._query_file = resource_data["file"]
-    #         self.save_report(report)
-    #         # print(resource_data["file"])
-    #     # print(self.output_path)
-    #     # self.save_report(report)
+                    # Execute the sub-query with all necessary replacements
+                    self._get_result(
+                        sub_query, data["uri"], replace_dict=replace_dict
+                    )
+            else:
+                # Execute simple queries directly
+                self._get_result(query, data["uri"])
+
+        # Store the query results for this resource type
+        self.resource_types[resource_type]["queries"] = queries
+
+    # Add timestamp to track when the metrics were last updated
+    self.resource_types["timestamp"] = datetime.now().isoformat(
+        timespec="minutes"
+    )
+
+    # Save all results to JSON file
+    self.save_to_json(self.resource_types, "reports/metrics/resources.json")
 
 
 class MetricsRunner(ABC):
-- 
GitLab


From 0293eb0eb9725dd37abfd5a1904c698fc1fcefe6 Mon Sep 17 00:00:00 2001
From: Ralf Klammer <ralf.klammer@tu-dresden.de>
Date: Mon, 17 Mar 2025 13:20:31 +0100
Subject: [PATCH 19/59] [metrics] Uni-/Simplify metric runners (only 1
 run-function)

---
 reports/metrics/resources.json        | 2162 +++++++++++--------------
 scripts/kg_analysis/metrics_runner.py |  351 ++--
 2 files changed, 1122 insertions(+), 1391 deletions(-)

diff --git a/reports/metrics/resources.json b/reports/metrics/resources.json
index 89dfba1..ca20225 100644
--- a/reports/metrics/resources.json
+++ b/reports/metrics/resources.json
@@ -1,1275 +1,1011 @@
 {
     "dataset": {
-        "file": "06_dataset.md",
-        "uri": "<http://www.w3.org/ns/dcat#Dataset>",
-        "queries": {
-            "instances": {
-                "name": "Number of Resources",
-                "file": "RM001_instances_template.rq",
-                "execute": true,
-                "result": "724903",
-                "execution_time": 0.52
-            },
-            "assertions": {
-                "name": "Number of Assertions",
-                "file": "RM002_assertions_template.rq",
-                "execute": true,
-                "result": "724904",
-                "execution_time": 0.02
-            },
-            "linkage": {
-                "name": "Average Linkage",
-                "file": "RM003_linkage_template.rq",
-                "execute": true,
-                "result": "68",
-                "execution_time": 0.07
-            },
-            "edges_out": {
-                "name": "Edges - outgoing",
-                "files": {
-                    "total": {
-                        "name": "Total number of outgoing edges",
-                        "file": "RM004_1_out_edges_total_template.rq",
-                        "execute": true,
-                        "result": "724903",
-                        "execution_time": 4.03
-                    },
-                    "min": {
-                        "name": "Minimum of outgoing edges",
-                        "file": "RM004_2_out_edges_min_template.rq",
-                        "execute": true,
-                        "result": "6",
-                        "execution_time": 0.83
-                    },
-                    "median": {
-                        "name": "Median of outgoing edges",
-                        "file": "RM004_3_out_edges_median_template.rq",
-                        "replace_dict": {
-                            "{median_position}": "RM004_1_out_edges_total_template.rq"
-                        },
-                        "execute": true,
-                        "result": "23",
-                        "execution_time": 1.46
-                    },
-                    "max": {
-                        "name": "Maximum of outgoing edges",
-                        "file": "RM004_4_out_edges_max_template.rq",
-                        "execute": true,
-                        "result": "12519",
-                        "execution_time": 0.78
-                    }
+        "instances": {
+            "name": "Number of Resources",
+            "file": "RM001_instances_template.rq",
+            "execute": true,
+            "result": "724903",
+            "execution_time": 0.01
+        },
+        "assertions": {
+            "name": "Number of Assertions",
+            "file": "RM002_assertions_template.rq",
+            "execute": true,
+            "result": "724904",
+            "execution_time": 0.01
+        },
+        "linkage": {
+            "name": "Average Linkage",
+            "file": "RM003_linkage_template.rq",
+            "execute": false
+        },
+        "edges_out": {
+            "name": "Edges - outgoing",
+            "files": {
+                "total": {
+                    "name": "Total number of outgoing edges",
+                    "file": "RM004_1_out_edges_total_template.rq",
+                    "execute": false
+                },
+                "min": {
+                    "name": "Minimum of outgoing edges",
+                    "file": "RM004_2_out_edges_min_template.rq",
+                    "execute": false
+                },
+                "median": {
+                    "name": "Median of outgoing edges",
+                    "file": "RM004_3_out_edges_median_template.rq",
+                    "replace_dict": {
+                        "{median_position}": "RM004_1_out_edges_total_template.rq"
+                    },
+                    "execute": false
+                },
+                "max": {
+                    "name": "Maximum of outgoing edges",
+                    "file": "RM004_4_out_edges_max_template.rq",
+                    "execute": false
                 }
-            },
-            "edges_in": {
-                "name": "Edges - incoming",
-                "files": {
-                    "total": {
-                        "name": "Total number of incoming edges",
-                        "file": "RM005_1_in_edges_total_template.rq",
-                        "execute": true,
-                        "result": "4",
-                        "execution_time": 0.12
-                    },
-                    "min": {
-                        "name": "Minimum of incoming edges",
-                        "file": "RM005_2_in_edges_min_template.rq",
-                        "execute": true,
-                        "result": "1",
-                        "execution_time": 0.05
-                    },
-                    "median": {
-                        "name": "Median of incoming edges",
-                        "file": "RM005_3_in_edges_median_template.rq",
-                        "replace_dict": {
-                            "{median_position}": "RM005_1_in_edges_total_template.rq"
-                        },
-                        "execute": true,
-                        "result": "1",
-                        "execution_time": 0.05
-                    },
-                    "max": {
-                        "name": "Maximum of incoming edges",
-                        "file": "RM005_4_in_edges_max_template.rq",
-                        "execute": true,
-                        "result": "107",
-                        "execution_time": 1.1
-                    }
+            }
+        },
+        "edges_in": {
+            "name": "Edges - incoming",
+            "files": {
+                "total": {
+                    "name": "Total number of incoming edges",
+                    "file": "RM005_1_in_edges_total_template.rq",
+                    "execute": false
+                },
+                "min": {
+                    "name": "Minimum of incoming edges",
+                    "file": "RM005_2_in_edges_min_template.rq",
+                    "execute": false
+                },
+                "median": {
+                    "name": "Median of incoming edges",
+                    "file": "RM005_3_in_edges_median_template.rq",
+                    "replace_dict": {
+                        "{median_position}": "RM005_1_in_edges_total_template.rq"
+                    },
+                    "execute": false
+                },
+                "max": {
+                    "name": "Maximum of incoming edges",
+                    "file": "RM005_4_in_edges_max_template.rq",
+                    "execute": false
                 }
-            },
-            "connectivity": {
-                "name": "Connectivity",
-                "file": "RM006_connectivity_template.rq",
-                "execute": true,
-                "result": "427594",
-                "execution_time": 4.33
             }
-        }
+        },
+        "connectivity": {
+            "name": "Connectivity",
+            "file": "RM006_connectivity_template.rq",
+            "execute": false
+        },
+        "timestamp": "2025-03-17T10:10",
+        "file": "06_dataset.md"
     },
     "publication": {
-        "file": "07_publication.md",
-        "uri": "<http://nfdi4earth.de/ontology/Registry>",
-        "queries": {
-            "instances": {
-                "name": "Number of Resources",
-                "file": "RM001_instances_template.rq",
-                "execute": true,
-                "result": "5",
-                "execution_time": 0.01
-            },
-            "assertions": {
-                "name": "Number of Assertions",
-                "file": "RM002_assertions_template.rq",
-                "execute": true,
-                "result": "6",
-                "execution_time": 0.01
-            },
-            "linkage": {
-                "name": "Average Linkage",
-                "file": "RM003_linkage_template.rq",
-                "execute": true,
-                "result": 0,
-                "execution_time": 0.02
-            },
-            "edges_out": {
-                "name": "Edges - outgoing",
-                "files": {
-                    "total": {
-                        "name": "Total number of outgoing edges",
-                        "file": "RM004_1_out_edges_total_template.rq",
-                        "execute": true,
-                        "result": "5",
-                        "execution_time": 0.01
-                    },
-                    "min": {
-                        "name": "Minimum of outgoing edges",
-                        "file": "RM004_2_out_edges_min_template.rq",
-                        "execute": true,
-                        "result": "7",
-                        "execution_time": 0.02
-                    },
-                    "median": {
-                        "name": "Median of outgoing edges",
-                        "file": "RM004_3_out_edges_median_template.rq",
-                        "replace_dict": {
-                            "{median_position}": "RM004_1_out_edges_total_template.rq"
-                        },
-                        "execute": true,
-                        "result": "9",
-                        "execution_time": 0.02
-                    },
-                    "max": {
-                        "name": "Maximum of outgoing edges",
-                        "file": "RM004_4_out_edges_max_template.rq",
-                        "execute": true,
-                        "result": "11",
-                        "execution_time": 0.01
-                    }
+        "instances": {
+            "name": "Number of Resources",
+            "file": "RM001_instances_template.rq",
+            "execute": true,
+            "result": "5",
+            "execution_time": 0.01
+        },
+        "assertions": {
+            "name": "Number of Assertions",
+            "file": "RM002_assertions_template.rq",
+            "execute": true,
+            "result": "6",
+            "execution_time": 0.01
+        },
+        "linkage": {
+            "name": "Average Linkage",
+            "file": "RM003_linkage_template.rq",
+            "execute": false
+        },
+        "edges_out": {
+            "name": "Edges - outgoing",
+            "files": {
+                "total": {
+                    "name": "Total number of outgoing edges",
+                    "file": "RM004_1_out_edges_total_template.rq",
+                    "execute": false
+                },
+                "min": {
+                    "name": "Minimum of outgoing edges",
+                    "file": "RM004_2_out_edges_min_template.rq",
+                    "execute": false
+                },
+                "median": {
+                    "name": "Median of outgoing edges",
+                    "file": "RM004_3_out_edges_median_template.rq",
+                    "replace_dict": {
+                        "{median_position}": "RM004_1_out_edges_total_template.rq"
+                    },
+                    "execute": false
+                },
+                "max": {
+                    "name": "Maximum of outgoing edges",
+                    "file": "RM004_4_out_edges_max_template.rq",
+                    "execute": false
                 }
-            },
-            "edges_in": {
-                "name": "Edges - incoming",
-                "files": {
-                    "total": {
-                        "name": "Total number of incoming edges",
-                        "file": "RM005_1_in_edges_total_template.rq",
-                        "execute": true,
-                        "result": "0",
-                        "execution_time": 0.02
-                    },
-                    "min": {
-                        "name": "Minimum of incoming edges",
-                        "file": "RM005_2_in_edges_min_template.rq",
-                        "execute": true,
-                        "result": "-",
-                        "execution_time": 0.01
-                    },
-                    "median": {
-                        "name": "Median of incoming edges",
-                        "file": "RM005_3_in_edges_median_template.rq",
-                        "replace_dict": {
-                            "{median_position}": "RM005_1_in_edges_total_template.rq"
-                        },
-                        "execute": true,
-                        "result": "-",
-                        "execution_time": 0.02
-                    },
-                    "max": {
-                        "name": "Maximum of incoming edges",
-                        "file": "RM005_4_in_edges_max_template.rq",
-                        "execute": true,
-                        "result": "-",
-                        "execution_time": 0.01
-                    }
+            }
+        },
+        "edges_in": {
+            "name": "Edges - incoming",
+            "files": {
+                "total": {
+                    "name": "Total number of incoming edges",
+                    "file": "RM005_1_in_edges_total_template.rq",
+                    "execute": false
+                },
+                "min": {
+                    "name": "Minimum of incoming edges",
+                    "file": "RM005_2_in_edges_min_template.rq",
+                    "execute": false
+                },
+                "median": {
+                    "name": "Median of incoming edges",
+                    "file": "RM005_3_in_edges_median_template.rq",
+                    "replace_dict": {
+                        "{median_position}": "RM005_1_in_edges_total_template.rq"
+                    },
+                    "execute": false
+                },
+                "max": {
+                    "name": "Maximum of incoming edges",
+                    "file": "RM005_4_in_edges_max_template.rq",
+                    "execute": false
                 }
-            },
-            "connectivity": {
-                "name": "Connectivity",
-                "file": "RM006_connectivity_template.rq",
-                "execute": true,
-                "result": "15",
-                "execution_time": 0.01
             }
-        }
+        },
+        "connectivity": {
+            "name": "Connectivity",
+            "file": "RM006_connectivity_template.rq",
+            "execute": false
+        },
+        "timestamp": "2025-03-17T10:10",
+        "file": "07_publication.md"
     },
     "learning_resource": {
-        "file": "08_learning_resource.md",
-        "uri": "<http://schema.org/LearningResource>",
-        "queries": {
-            "instances": {
-                "name": "Number of Resources",
-                "file": "RM001_instances_template.rq",
-                "execute": true,
-                "result": "512",
-                "execution_time": 0.02
-            },
-            "assertions": {
-                "name": "Number of Assertions",
-                "file": "RM002_assertions_template.rq",
-                "execute": true,
-                "result": "513",
-                "execution_time": 0.02
-            },
-            "linkage": {
-                "name": "Average Linkage",
-                "file": "RM003_linkage_template.rq",
-                "execute": true,
-                "result": "25",
-                "execution_time": 0.02
-            },
-            "edges_out": {
-                "name": "Edges - outgoing",
-                "files": {
-                    "total": {
-                        "name": "Total number of outgoing edges",
-                        "file": "RM004_1_out_edges_total_template.rq",
-                        "execute": true,
-                        "result": "512",
-                        "execution_time": 0.01
-                    },
-                    "min": {
-                        "name": "Minimum of outgoing edges",
-                        "file": "RM004_2_out_edges_min_template.rq",
-                        "execute": true,
-                        "result": "10",
-                        "execution_time": 0.02
-                    },
-                    "median": {
-                        "name": "Median of outgoing edges",
-                        "file": "RM004_3_out_edges_median_template.rq",
-                        "replace_dict": {
-                            "{median_position}": "RM004_1_out_edges_total_template.rq"
-                        },
-                        "execute": true,
-                        "result": "15",
-                        "execution_time": 0.02
-                    },
-                    "max": {
-                        "name": "Maximum of outgoing edges",
-                        "file": "RM004_4_out_edges_max_template.rq",
-                        "execute": true,
-                        "result": "27",
-                        "execution_time": 0.02
-                    }
+        "instances": {
+            "name": "Number of Resources",
+            "file": "RM001_instances_template.rq",
+            "execute": true,
+            "result": "512",
+            "execution_time": 0.01
+        },
+        "assertions": {
+            "name": "Number of Assertions",
+            "file": "RM002_assertions_template.rq",
+            "execute": true,
+            "result": "513",
+            "execution_time": 0.01
+        },
+        "linkage": {
+            "name": "Average Linkage",
+            "file": "RM003_linkage_template.rq",
+            "execute": false
+        },
+        "edges_out": {
+            "name": "Edges - outgoing",
+            "files": {
+                "total": {
+                    "name": "Total number of outgoing edges",
+                    "file": "RM004_1_out_edges_total_template.rq",
+                    "execute": false
+                },
+                "min": {
+                    "name": "Minimum of outgoing edges",
+                    "file": "RM004_2_out_edges_min_template.rq",
+                    "execute": false
+                },
+                "median": {
+                    "name": "Median of outgoing edges",
+                    "file": "RM004_3_out_edges_median_template.rq",
+                    "replace_dict": {
+                        "{median_position}": "RM004_1_out_edges_total_template.rq"
+                    },
+                    "execute": false
+                },
+                "max": {
+                    "name": "Maximum of outgoing edges",
+                    "file": "RM004_4_out_edges_max_template.rq",
+                    "execute": false
                 }
-            },
-            "edges_in": {
-                "name": "Edges - incoming",
-                "files": {
-                    "total": {
-                        "name": "Total number of incoming edges",
-                        "file": "RM005_1_in_edges_total_template.rq",
-                        "execute": true,
-                        "result": "1",
-                        "execution_time": 0.01
-                    },
-                    "min": {
-                        "name": "Minimum of incoming edges",
-                        "file": "RM005_2_in_edges_min_template.rq",
-                        "execute": true,
-                        "result": "3",
-                        "execution_time": 0.02
-                    },
-                    "median": {
-                        "name": "Median of incoming edges",
-                        "file": "RM005_3_in_edges_median_template.rq",
-                        "replace_dict": {
-                            "{median_position}": "RM005_1_in_edges_total_template.rq"
-                        },
-                        "execute": true,
-                        "result": "3",
-                        "execution_time": 0.01
-                    },
-                    "max": {
-                        "name": "Maximum of incoming edges",
-                        "file": "RM005_4_in_edges_max_template.rq",
-                        "execute": true,
-                        "result": "3",
-                        "execution_time": 0.02
-                    }
+            }
+        },
+        "edges_in": {
+            "name": "Edges - incoming",
+            "files": {
+                "total": {
+                    "name": "Total number of incoming edges",
+                    "file": "RM005_1_in_edges_total_template.rq",
+                    "execute": false
+                },
+                "min": {
+                    "name": "Minimum of incoming edges",
+                    "file": "RM005_2_in_edges_min_template.rq",
+                    "execute": false
+                },
+                "median": {
+                    "name": "Median of incoming edges",
+                    "file": "RM005_3_in_edges_median_template.rq",
+                    "replace_dict": {
+                        "{median_position}": "RM005_1_in_edges_total_template.rq"
+                    },
+                    "execute": false
+                },
+                "max": {
+                    "name": "Maximum of incoming edges",
+                    "file": "RM005_4_in_edges_max_template.rq",
+                    "execute": false
                 }
-            },
-            "connectivity": {
-                "name": "Connectivity",
-                "file": "RM006_connectivity_template.rq",
-                "execute": true,
-                "result": "530",
-                "execution_time": 0.02
             }
-        }
+        },
+        "connectivity": {
+            "name": "Connectivity",
+            "file": "RM006_connectivity_template.rq",
+            "execute": false
+        },
+        "timestamp": "2025-03-17T10:10",
+        "file": "08_learning_resource.md"
     },
     "repository": {
-        "file": "09_repository.md",
-        "uri": "<http://nfdi4earth.de/ontology/Repository>",
-        "queries": {
-            "instances": {
-                "name": "Number of Resources",
-                "file": "RM001_instances_template.rq",
-                "execute": true,
-                "result": "159",
-                "execution_time": 0.02
-            },
-            "assertions": {
-                "name": "Number of Assertions",
-                "file": "RM002_assertions_template.rq",
-                "execute": true,
-                "result": "162",
-                "execution_time": 0.01
-            },
-            "linkage": {
-                "name": "Average Linkage",
-                "file": "RM003_linkage_template.rq",
-                "execute": true,
-                "result": "90212",
-                "execution_time": 0.02
-            },
-            "edges_out": {
-                "name": "Edges - outgoing",
-                "files": {
-                    "total": {
-                        "name": "Total number of outgoing edges",
-                        "file": "RM004_1_out_edges_total_template.rq",
-                        "execute": true,
-                        "result": "159",
-                        "execution_time": 0.01
-                    },
-                    "min": {
-                        "name": "Minimum of outgoing edges",
-                        "file": "RM004_2_out_edges_min_template.rq",
-                        "execute": true,
-                        "result": "7",
-                        "execution_time": 0.02
-                    },
-                    "median": {
-                        "name": "Median of outgoing edges",
-                        "file": "RM004_3_out_edges_median_template.rq",
-                        "replace_dict": {
-                            "{median_position}": "RM004_1_out_edges_total_template.rq"
-                        },
-                        "execute": true,
-                        "result": "44",
-                        "execution_time": 0.02
-                    },
-                    "max": {
-                        "name": "Maximum of outgoing edges",
-                        "file": "RM004_4_out_edges_max_template.rq",
-                        "execute": true,
-                        "result": "92",
-                        "execution_time": 0.02
-                    }
+        "instances": {
+            "name": "Number of Resources",
+            "file": "RM001_instances_template.rq",
+            "execute": true,
+            "result": "159",
+            "execution_time": 0.01
+        },
+        "assertions": {
+            "name": "Number of Assertions",
+            "file": "RM002_assertions_template.rq",
+            "execute": true,
+            "result": "162",
+            "execution_time": 0.01
+        },
+        "linkage": {
+            "name": "Average Linkage",
+            "file": "RM003_linkage_template.rq",
+            "execute": false
+        },
+        "edges_out": {
+            "name": "Edges - outgoing",
+            "files": {
+                "total": {
+                    "name": "Total number of outgoing edges",
+                    "file": "RM004_1_out_edges_total_template.rq",
+                    "execute": false
+                },
+                "min": {
+                    "name": "Minimum of outgoing edges",
+                    "file": "RM004_2_out_edges_min_template.rq",
+                    "execute": false
+                },
+                "median": {
+                    "name": "Median of outgoing edges",
+                    "file": "RM004_3_out_edges_median_template.rq",
+                    "replace_dict": {
+                        "{median_position}": "RM004_1_out_edges_total_template.rq"
+                    },
+                    "execute": false
+                },
+                "max": {
+                    "name": "Maximum of outgoing edges",
+                    "file": "RM004_4_out_edges_max_template.rq",
+                    "execute": false
                 }
-            },
-            "edges_in": {
-                "name": "Edges - incoming",
-                "files": {
-                    "total": {
-                        "name": "Total number of incoming edges",
-                        "file": "RM005_1_in_edges_total_template.rq",
-                        "execute": true,
-                        "result": "5",
-                        "execution_time": 0.06
-                    },
-                    "min": {
-                        "name": "Minimum of incoming edges",
-                        "file": "RM005_2_in_edges_min_template.rq",
-                        "execute": true,
-                        "result": "1417",
-                        "execution_time": 0.03
-                    },
-                    "median": {
-                        "name": "Median of incoming edges",
-                        "file": "RM005_3_in_edges_median_template.rq",
-                        "replace_dict": {
-                            "{median_position}": "RM005_1_in_edges_total_template.rq"
-                        },
-                        "execute": true,
-                        "result": "6718",
-                        "execution_time": 0.04
-                    },
-                    "max": {
-                        "name": "Maximum of incoming edges",
-                        "file": "RM005_4_in_edges_max_template.rq",
-                        "execute": true,
-                        "result": "432941",
-                        "execution_time": 0.03
-                    }
+            }
+        },
+        "edges_in": {
+            "name": "Edges - incoming",
+            "files": {
+                "total": {
+                    "name": "Total number of incoming edges",
+                    "file": "RM005_1_in_edges_total_template.rq",
+                    "execute": false
+                },
+                "min": {
+                    "name": "Minimum of incoming edges",
+                    "file": "RM005_2_in_edges_min_template.rq",
+                    "execute": false
+                },
+                "median": {
+                    "name": "Median of incoming edges",
+                    "file": "RM005_3_in_edges_median_template.rq",
+                    "replace_dict": {
+                        "{median_position}": "RM005_1_in_edges_total_template.rq"
+                    },
+                    "execute": false
+                },
+                "max": {
+                    "name": "Maximum of incoming edges",
+                    "file": "RM005_4_in_edges_max_template.rq",
+                    "execute": false
                 }
-            },
-            "connectivity": {
-                "name": "Connectivity",
-                "file": "RM006_connectivity_template.rq",
-                "execute": true,
-                "result": "825",
-                "execution_time": 0.02
             }
-        }
+        },
+        "connectivity": {
+            "name": "Connectivity",
+            "file": "RM006_connectivity_template.rq",
+            "execute": false
+        },
+        "timestamp": "2025-03-17T10:10",
+        "file": "09_repository.md"
     },
     "article_lhb": {
-        "file": "10_article_lhb.md",
-        "uri": "<http://nfdi4earth.de/ontology/LHBArticle>",
-        "queries": {
-            "instances": {
-                "name": "Number of Resources",
-                "file": "RM001_instances_template.rq",
-                "execute": true,
-                "result": "114",
-                "execution_time": 0.01
-            },
-            "assertions": {
-                "name": "Number of Assertions",
-                "file": "RM002_assertions_template.rq",
-                "execute": true,
-                "result": "117",
-                "execution_time": 0.01
-            },
-            "linkage": {
-                "name": "Average Linkage",
-                "file": "RM003_linkage_template.rq",
-                "execute": true,
-                "result": "33.245098039215686",
-                "execution_time": 0.02
-            },
-            "edges_out": {
-                "name": "Edges - outgoing",
-                "files": {
-                    "total": {
-                        "name": "Total number of outgoing edges",
-                        "file": "RM004_1_out_edges_total_template.rq",
-                        "execute": true,
-                        "result": "114",
-                        "execution_time": 1.04
-                    },
-                    "min": {
-                        "name": "Minimum of outgoing edges",
-                        "file": "RM004_2_out_edges_min_template.rq",
-                        "execute": true,
-                        "result": "11",
-                        "execution_time": 0.01
-                    },
-                    "median": {
-                        "name": "Median of outgoing edges",
-                        "file": "RM004_3_out_edges_median_template.rq",
-                        "replace_dict": {
-                            "{median_position}": "RM004_1_out_edges_total_template.rq"
-                        },
-                        "execute": true,
-                        "result": "30",
-                        "execution_time": 0.01
-                    },
-                    "max": {
-                        "name": "Maximum of outgoing edges",
-                        "file": "RM004_4_out_edges_max_template.rq",
-                        "execute": true,
-                        "result": "62",
-                        "execution_time": 0.01
-                    }
+        "instances": {
+            "name": "Number of Resources",
+            "file": "RM001_instances_template.rq",
+            "execute": true,
+            "result": "114",
+            "execution_time": 0.01
+        },
+        "assertions": {
+            "name": "Number of Assertions",
+            "file": "RM002_assertions_template.rq",
+            "execute": true,
+            "result": "117",
+            "execution_time": 0.01
+        },
+        "linkage": {
+            "name": "Average Linkage",
+            "file": "RM003_linkage_template.rq",
+            "execute": false
+        },
+        "edges_out": {
+            "name": "Edges - outgoing",
+            "files": {
+                "total": {
+                    "name": "Total number of outgoing edges",
+                    "file": "RM004_1_out_edges_total_template.rq",
+                    "execute": false
+                },
+                "min": {
+                    "name": "Minimum of outgoing edges",
+                    "file": "RM004_2_out_edges_min_template.rq",
+                    "execute": false
+                },
+                "median": {
+                    "name": "Median of outgoing edges",
+                    "file": "RM004_3_out_edges_median_template.rq",
+                    "replace_dict": {
+                        "{median_position}": "RM004_1_out_edges_total_template.rq"
+                    },
+                    "execute": false
+                },
+                "max": {
+                    "name": "Maximum of outgoing edges",
+                    "file": "RM004_4_out_edges_max_template.rq",
+                    "execute": false
                 }
-            },
-            "edges_in": {
-                "name": "Edges - incoming",
-                "files": {
-                    "total": {
-                        "name": "Total number of incoming edges",
-                        "file": "RM005_1_in_edges_total_template.rq",
-                        "execute": true,
-                        "result": "102",
-                        "execution_time": 0.01
-                    },
-                    "min": {
-                        "name": "Minimum of incoming edges",
-                        "file": "RM005_2_in_edges_min_template.rq",
-                        "execute": true,
-                        "result": "1",
-                        "execution_time": 0.02
-                    },
-                    "median": {
-                        "name": "Median of incoming edges",
-                        "file": "RM005_3_in_edges_median_template.rq",
-                        "replace_dict": {
-                            "{median_position}": "RM005_1_in_edges_total_template.rq"
-                        },
-                        "execute": true,
-                        "result": "2",
-                        "execution_time": 1.06
-                    },
-                    "max": {
-                        "name": "Maximum of incoming edges",
-                        "file": "RM005_4_in_edges_max_template.rq",
-                        "execute": true,
-                        "result": "24",
-                        "execution_time": 0.01
-                    }
+            }
+        },
+        "edges_in": {
+            "name": "Edges - incoming",
+            "files": {
+                "total": {
+                    "name": "Total number of incoming edges",
+                    "file": "RM005_1_in_edges_total_template.rq",
+                    "execute": false
+                },
+                "min": {
+                    "name": "Minimum of incoming edges",
+                    "file": "RM005_2_in_edges_min_template.rq",
+                    "execute": false
+                },
+                "median": {
+                    "name": "Median of incoming edges",
+                    "file": "RM005_3_in_edges_median_template.rq",
+                    "replace_dict": {
+                        "{median_position}": "RM005_1_in_edges_total_template.rq"
+                    },
+                    "execute": false
+                },
+                "max": {
+                    "name": "Maximum of incoming edges",
+                    "file": "RM005_4_in_edges_max_template.rq",
+                    "execute": false
                 }
-            },
-            "connectivity": {
-                "name": "Connectivity",
-                "file": "RM006_connectivity_template.rq",
-                "execute": true,
-                "result": "592",
-                "execution_time": 0.01
             }
-        }
+        },
+        "connectivity": {
+            "name": "Connectivity",
+            "file": "RM006_connectivity_template.rq",
+            "execute": false
+        },
+        "timestamp": "2025-03-17T10:10",
+        "file": "10_article_lhb.md"
     },
     "standards": {
-        "file": "11_standards.md",
-        "uri": "<http://nfdi4earth.de/ontology/MetadataStandard>",
-        "queries": {
-            "instances": {
-                "name": "Number of Resources",
-                "file": "RM001_instances_template.rq",
-                "execute": true,
-                "result": "89",
-                "execution_time": 1.07
-            },
-            "assertions": {
-                "name": "Number of Assertions",
-                "file": "RM002_assertions_template.rq",
-                "execute": true,
-                "result": "101",
-                "execution_time": 0.01
-            },
-            "linkage": {
-                "name": "Average Linkage",
-                "file": "RM003_linkage_template.rq",
-                "execute": true,
-                "result": "13.714285714285714",
-                "execution_time": 1.02
-            },
-            "edges_out": {
-                "name": "Edges - outgoing",
-                "files": {
-                    "total": {
-                        "name": "Total number of outgoing edges",
-                        "file": "RM004_1_out_edges_total_template.rq",
-                        "execute": true,
-                        "result": "89",
-                        "execution_time": 0.01
-                    },
-                    "min": {
-                        "name": "Minimum of outgoing edges",
-                        "file": "RM004_2_out_edges_min_template.rq",
-                        "execute": true,
-                        "result": "5",
-                        "execution_time": 0.02
-                    },
-                    "median": {
-                        "name": "Median of outgoing edges",
-                        "file": "RM004_3_out_edges_median_template.rq",
-                        "replace_dict": {
-                            "{median_position}": "RM004_1_out_edges_total_template.rq"
-                        },
-                        "execute": true,
-                        "result": "9",
-                        "execution_time": 0.01
-                    },
-                    "max": {
-                        "name": "Maximum of outgoing edges",
-                        "file": "RM004_4_out_edges_max_template.rq",
-                        "execute": true,
-                        "result": "27",
-                        "execution_time": 0.01
-                    }
+        "instances": {
+            "name": "Number of Resources",
+            "file": "RM001_instances_template.rq",
+            "execute": true,
+            "result": "89",
+            "execution_time": 0.01
+        },
+        "assertions": {
+            "name": "Number of Assertions",
+            "file": "RM002_assertions_template.rq",
+            "execute": true,
+            "result": "101",
+            "execution_time": 0.01
+        },
+        "linkage": {
+            "name": "Average Linkage",
+            "file": "RM003_linkage_template.rq",
+            "execute": false
+        },
+        "edges_out": {
+            "name": "Edges - outgoing",
+            "files": {
+                "total": {
+                    "name": "Total number of outgoing edges",
+                    "file": "RM004_1_out_edges_total_template.rq",
+                    "execute": false
+                },
+                "min": {
+                    "name": "Minimum of outgoing edges",
+                    "file": "RM004_2_out_edges_min_template.rq",
+                    "execute": false
+                },
+                "median": {
+                    "name": "Median of outgoing edges",
+                    "file": "RM004_3_out_edges_median_template.rq",
+                    "replace_dict": {
+                        "{median_position}": "RM004_1_out_edges_total_template.rq"
+                    },
+                    "execute": false
+                },
+                "max": {
+                    "name": "Maximum of outgoing edges",
+                    "file": "RM004_4_out_edges_max_template.rq",
+                    "execute": false
                 }
-            },
-            "edges_in": {
-                "name": "Edges - incoming",
-                "files": {
-                    "total": {
-                        "name": "Total number of incoming edges",
-                        "file": "RM005_1_in_edges_total_template.rq",
-                        "execute": true,
-                        "result": "77",
-                        "execution_time": 0.01
-                    },
-                    "min": {
-                        "name": "Minimum of incoming edges",
-                        "file": "RM005_2_in_edges_min_template.rq",
-                        "execute": true,
-                        "result": "1",
-                        "execution_time": 0.01
-                    },
-                    "median": {
-                        "name": "Median of incoming edges",
-                        "file": "RM005_3_in_edges_median_template.rq",
-                        "replace_dict": {
-                            "{median_position}": "RM005_1_in_edges_total_template.rq"
-                        },
-                        "execute": true,
-                        "result": "1",
-                        "execution_time": 0.02
-                    },
-                    "max": {
-                        "name": "Maximum of incoming edges",
-                        "file": "RM005_4_in_edges_max_template.rq",
-                        "execute": true,
-                        "result": "56",
-                        "execution_time": 0.02
-                    }
+            }
+        },
+        "edges_in": {
+            "name": "Edges - incoming",
+            "files": {
+                "total": {
+                    "name": "Total number of incoming edges",
+                    "file": "RM005_1_in_edges_total_template.rq",
+                    "execute": false
+                },
+                "min": {
+                    "name": "Minimum of incoming edges",
+                    "file": "RM005_2_in_edges_min_template.rq",
+                    "execute": false
+                },
+                "median": {
+                    "name": "Median of incoming edges",
+                    "file": "RM005_3_in_edges_median_template.rq",
+                    "replace_dict": {
+                        "{median_position}": "RM005_1_in_edges_total_template.rq"
+                    },
+                    "execute": false
+                },
+                "max": {
+                    "name": "Maximum of incoming edges",
+                    "file": "RM005_4_in_edges_max_template.rq",
+                    "execute": false
                 }
-            },
-            "connectivity": {
-                "name": "Connectivity",
-                "file": "RM006_connectivity_template.rq",
-                "execute": true,
-                "result": "100",
-                "execution_time": 0.02
             }
-        }
+        },
+        "connectivity": {
+            "name": "Connectivity",
+            "file": "RM006_connectivity_template.rq",
+            "execute": false
+        },
+        "timestamp": "2025-03-17T10:10",
+        "file": "11_standards.md"
     },
     "software": {
-        "file": "13_software.md",
-        "uri": "<http://schema.org/SoftwareSourceCode>",
-        "queries": {
-            "instances": {
-                "name": "Number of Resources",
-                "file": "RM001_instances_template.rq",
-                "execute": true,
-                "result": "147",
-                "execution_time": 0.01
-            },
-            "assertions": {
-                "name": "Number of Assertions",
-                "file": "RM002_assertions_template.rq",
-                "execute": true,
-                "result": "148",
-                "execution_time": 0.01
-            },
-            "linkage": {
-                "name": "Average Linkage",
-                "file": "RM003_linkage_template.rq",
-                "execute": true,
-                "result": "11.5",
-                "execution_time": 0.04
-            },
-            "edges_out": {
-                "name": "Edges - outgoing",
-                "files": {
-                    "total": {
-                        "name": "Total number of outgoing edges",
-                        "file": "RM004_1_out_edges_total_template.rq",
-                        "execute": true,
-                        "result": "147",
-                        "execution_time": 0.01
-                    },
-                    "min": {
-                        "name": "Minimum of outgoing edges",
-                        "file": "RM004_2_out_edges_min_template.rq",
-                        "execute": true,
-                        "result": "7",
-                        "execution_time": 0.01
-                    },
-                    "median": {
-                        "name": "Median of outgoing edges",
-                        "file": "RM004_3_out_edges_median_template.rq",
-                        "replace_dict": {
-                            "{median_position}": "RM004_1_out_edges_total_template.rq"
-                        },
-                        "execute": true,
-                        "result": "20",
-                        "execution_time": 0.01
-                    },
-                    "max": {
-                        "name": "Maximum of outgoing edges",
-                        "file": "RM004_4_out_edges_max_template.rq",
-                        "execute": true,
-                        "result": "122",
-                        "execution_time": 0.01
-                    }
+        "instances": {
+            "name": "Number of Resources",
+            "file": "RM001_instances_template.rq",
+            "execute": true,
+            "result": "147",
+            "execution_time": 0.01
+        },
+        "assertions": {
+            "name": "Number of Assertions",
+            "file": "RM002_assertions_template.rq",
+            "execute": true,
+            "result": "148",
+            "execution_time": 0.01
+        },
+        "linkage": {
+            "name": "Average Linkage",
+            "file": "RM003_linkage_template.rq",
+            "execute": false
+        },
+        "edges_out": {
+            "name": "Edges - outgoing",
+            "files": {
+                "total": {
+                    "name": "Total number of outgoing edges",
+                    "file": "RM004_1_out_edges_total_template.rq",
+                    "execute": false
+                },
+                "min": {
+                    "name": "Minimum of outgoing edges",
+                    "file": "RM004_2_out_edges_min_template.rq",
+                    "execute": false
+                },
+                "median": {
+                    "name": "Median of outgoing edges",
+                    "file": "RM004_3_out_edges_median_template.rq",
+                    "replace_dict": {
+                        "{median_position}": "RM004_1_out_edges_total_template.rq"
+                    },
+                    "execute": false
+                },
+                "max": {
+                    "name": "Maximum of outgoing edges",
+                    "file": "RM004_4_out_edges_max_template.rq",
+                    "execute": false
                 }
-            },
-            "edges_in": {
-                "name": "Edges - incoming",
-                "files": {
-                    "total": {
-                        "name": "Total number of incoming edges",
-                        "file": "RM005_1_in_edges_total_template.rq",
-                        "execute": true,
-                        "result": "8",
-                        "execution_time": 0.01
-                    },
-                    "min": {
-                        "name": "Minimum of incoming edges",
-                        "file": "RM005_2_in_edges_min_template.rq",
-                        "execute": true,
-                        "result": "1",
-                        "execution_time": 0.01
-                    },
-                    "median": {
-                        "name": "Median of incoming edges",
-                        "file": "RM005_3_in_edges_median_template.rq",
-                        "replace_dict": {
-                            "{median_position}": "RM005_1_in_edges_total_template.rq"
-                        },
-                        "execute": true,
-                        "result": "1",
-                        "execution_time": 0.02
-                    },
-                    "max": {
-                        "name": "Maximum of incoming edges",
-                        "file": "RM005_4_in_edges_max_template.rq",
-                        "execute": true,
-                        "result": "2",
-                        "execution_time": 0.02
-                    }
+            }
+        },
+        "edges_in": {
+            "name": "Edges - incoming",
+            "files": {
+                "total": {
+                    "name": "Total number of incoming edges",
+                    "file": "RM005_1_in_edges_total_template.rq",
+                    "execute": false
+                },
+                "min": {
+                    "name": "Minimum of incoming edges",
+                    "file": "RM005_2_in_edges_min_template.rq",
+                    "execute": false
+                },
+                "median": {
+                    "name": "Median of incoming edges",
+                    "file": "RM005_3_in_edges_median_template.rq",
+                    "replace_dict": {
+                        "{median_position}": "RM005_1_in_edges_total_template.rq"
+                    },
+                    "execute": false
+                },
+                "max": {
+                    "name": "Maximum of incoming edges",
+                    "file": "RM005_4_in_edges_max_template.rq",
+                    "execute": false
                 }
-            },
-            "connectivity": {
-                "name": "Connectivity",
-                "file": "RM006_connectivity_template.rq",
-                "execute": true,
-                "result": "1103",
-                "execution_time": 0.02
             }
-        }
+        },
+        "connectivity": {
+            "name": "Connectivity",
+            "file": "RM006_connectivity_template.rq",
+            "execute": false
+        },
+        "timestamp": "2025-03-17T10:10",
+        "file": "13_software.md"
     },
     "service": {
-        "file": "14_service.md",
-        "uri": "<http://www.w3.org/ns/sparql-service-description#Service>",
-        "queries": {
-            "instances": {
-                "name": "Number of Resources",
-                "file": "RM001_instances_template.rq",
-                "execute": true,
-                "result": "1",
-                "execution_time": 0.02
-            },
-            "assertions": {
-                "name": "Number of Assertions",
-                "file": "RM002_assertions_template.rq",
-                "execute": true,
-                "result": "1",
-                "execution_time": 1.06
-            },
-            "linkage": {
-                "name": "Average Linkage",
-                "file": "RM003_linkage_template.rq",
-                "execute": true,
-                "result": 0,
-                "execution_time": 0.02
-            },
-            "edges_out": {
-                "name": "Edges - outgoing",
-                "files": {
-                    "total": {
-                        "name": "Total number of outgoing edges",
-                        "file": "RM004_1_out_edges_total_template.rq",
-                        "execute": true,
-                        "result": "1",
-                        "execution_time": 0.02
-                    },
-                    "min": {
-                        "name": "Minimum of outgoing edges",
-                        "file": "RM004_2_out_edges_min_template.rq",
-                        "execute": true,
-                        "result": "14",
-                        "execution_time": 0.02
-                    },
-                    "median": {
-                        "name": "Median of outgoing edges",
-                        "file": "RM004_3_out_edges_median_template.rq",
-                        "replace_dict": {
-                            "{median_position}": "RM004_1_out_edges_total_template.rq"
-                        },
-                        "execute": true,
-                        "result": "14",
-                        "execution_time": 0.01
-                    },
-                    "max": {
-                        "name": "Maximum of outgoing edges",
-                        "file": "RM004_4_out_edges_max_template.rq",
-                        "execute": true,
-                        "result": "14",
-                        "execution_time": 0.01
-                    }
+        "instances": {
+            "name": "Number of Resources",
+            "file": "RM001_instances_template.rq",
+            "execute": true,
+            "result": "1",
+            "execution_time": 0.01
+        },
+        "assertions": {
+            "name": "Number of Assertions",
+            "file": "RM002_assertions_template.rq",
+            "execute": true,
+            "result": "1",
+            "execution_time": 0.01
+        },
+        "linkage": {
+            "name": "Average Linkage",
+            "file": "RM003_linkage_template.rq",
+            "execute": false
+        },
+        "edges_out": {
+            "name": "Edges - outgoing",
+            "files": {
+                "total": {
+                    "name": "Total number of outgoing edges",
+                    "file": "RM004_1_out_edges_total_template.rq",
+                    "execute": false
+                },
+                "min": {
+                    "name": "Minimum of outgoing edges",
+                    "file": "RM004_2_out_edges_min_template.rq",
+                    "execute": false
+                },
+                "median": {
+                    "name": "Median of outgoing edges",
+                    "file": "RM004_3_out_edges_median_template.rq",
+                    "replace_dict": {
+                        "{median_position}": "RM004_1_out_edges_total_template.rq"
+                    },
+                    "execute": false
+                },
+                "max": {
+                    "name": "Maximum of outgoing edges",
+                    "file": "RM004_4_out_edges_max_template.rq",
+                    "execute": false
                 }
-            },
-            "edges_in": {
-                "name": "Edges - incoming",
-                "files": {
-                    "total": {
-                        "name": "Total number of incoming edges",
-                        "file": "RM005_1_in_edges_total_template.rq",
-                        "execute": true,
-                        "result": "0",
-                        "execution_time": 0.01
-                    },
-                    "min": {
-                        "name": "Minimum of incoming edges",
-                        "file": "RM005_2_in_edges_min_template.rq",
-                        "execute": true,
-                        "result": "-",
-                        "execution_time": 0.02
-                    },
-                    "median": {
-                        "name": "Median of incoming edges",
-                        "file": "RM005_3_in_edges_median_template.rq",
-                        "replace_dict": {
-                            "{median_position}": "RM005_1_in_edges_total_template.rq"
-                        },
-                        "execute": true,
-                        "result": "-",
-                        "execution_time": 0.02
-                    },
-                    "max": {
-                        "name": "Maximum of incoming edges",
-                        "file": "RM005_4_in_edges_max_template.rq",
-                        "execute": true,
-                        "result": "-",
-                        "execution_time": 1.05
-                    }
+            }
+        },
+        "edges_in": {
+            "name": "Edges - incoming",
+            "files": {
+                "total": {
+                    "name": "Total number of incoming edges",
+                    "file": "RM005_1_in_edges_total_template.rq",
+                    "execute": false
+                },
+                "min": {
+                    "name": "Minimum of incoming edges",
+                    "file": "RM005_2_in_edges_min_template.rq",
+                    "execute": false
+                },
+                "median": {
+                    "name": "Median of incoming edges",
+                    "file": "RM005_3_in_edges_median_template.rq",
+                    "replace_dict": {
+                        "{median_position}": "RM005_1_in_edges_total_template.rq"
+                    },
+                    "execute": false
+                },
+                "max": {
+                    "name": "Maximum of incoming edges",
+                    "file": "RM005_4_in_edges_max_template.rq",
+                    "execute": false
                 }
-            },
-            "connectivity": {
-                "name": "Connectivity",
-                "file": "RM006_connectivity_template.rq",
-                "execute": true,
-                "result": "13",
-                "execution_time": 0.01
             }
-        }
+        },
+        "connectivity": {
+            "name": "Connectivity",
+            "file": "RM006_connectivity_template.rq",
+            "execute": false
+        },
+        "timestamp": "2025-03-17T10:10",
+        "file": "14_service.md"
     },
     "data_service": {
-        "file": "15_data_service.md",
-        "uri": "<http://www.w3.org/ns/dcat#DataService>",
-        "queries": {
-            "instances": {
-                "name": "Number of Resources",
-                "file": "RM001_instances_template.rq",
-                "execute": true,
-                "result": "363632",
-                "execution_time": 0.16
-            },
-            "assertions": {
-                "name": "Number of Assertions",
-                "file": "RM002_assertions_template.rq",
-                "execute": true,
-                "result": "363633",
-                "execution_time": 0.02
-            },
-            "linkage": {
-                "name": "Average Linkage",
-                "file": "RM003_linkage_template.rq",
-                "execute": true,
-                "result": 0,
-                "execution_time": 0.04
-            },
-            "edges_out": {
-                "name": "Edges - outgoing",
-                "files": {
-                    "total": {
-                        "name": "Total number of outgoing edges",
-                        "file": "RM004_1_out_edges_total_template.rq",
-                        "execute": true,
-                        "result": "363632",
-                        "execution_time": 1.71
-                    },
-                    "min": {
-                        "name": "Minimum of outgoing edges",
-                        "file": "RM004_2_out_edges_min_template.rq",
-                        "execute": true,
-                        "result": "11",
-                        "execution_time": 0.4
-                    },
-                    "median": {
-                        "name": "Median of outgoing edges",
-                        "file": "RM004_3_out_edges_median_template.rq",
-                        "replace_dict": {
-                            "{median_position}": "RM004_1_out_edges_total_template.rq"
-                        },
-                        "execute": true,
-                        "result": "28",
-                        "execution_time": 0.69
-                    },
-                    "max": {
-                        "name": "Maximum of outgoing edges",
-                        "file": "RM004_4_out_edges_max_template.rq",
-                        "execute": true,
-                        "result": "12519",
-                        "execution_time": 0.39
-                    }
+        "instances": {
+            "name": "Number of Resources",
+            "file": "RM001_instances_template.rq",
+            "execute": true,
+            "result": "363632",
+            "execution_time": 0.01
+        },
+        "assertions": {
+            "name": "Number of Assertions",
+            "file": "RM002_assertions_template.rq",
+            "execute": true,
+            "result": "363633",
+            "execution_time": 0.01
+        },
+        "linkage": {
+            "name": "Average Linkage",
+            "file": "RM003_linkage_template.rq",
+            "execute": false
+        },
+        "edges_out": {
+            "name": "Edges - outgoing",
+            "files": {
+                "total": {
+                    "name": "Total number of outgoing edges",
+                    "file": "RM004_1_out_edges_total_template.rq",
+                    "execute": false
+                },
+                "min": {
+                    "name": "Minimum of outgoing edges",
+                    "file": "RM004_2_out_edges_min_template.rq",
+                    "execute": false
+                },
+                "median": {
+                    "name": "Median of outgoing edges",
+                    "file": "RM004_3_out_edges_median_template.rq",
+                    "replace_dict": {
+                        "{median_position}": "RM004_1_out_edges_total_template.rq"
+                    },
+                    "execute": false
+                },
+                "max": {
+                    "name": "Maximum of outgoing edges",
+                    "file": "RM004_4_out_edges_max_template.rq",
+                    "execute": false
                 }
-            },
-            "edges_in": {
-                "name": "Edges - incoming",
-                "files": {
-                    "total": {
-                        "name": "Total number of incoming edges",
-                        "file": "RM005_1_in_edges_total_template.rq",
-                        "execute": true,
-                        "result": "0",
-                        "execution_time": 0.07
-                    },
-                    "min": {
-                        "name": "Minimum of incoming edges",
-                        "file": "RM005_2_in_edges_min_template.rq",
-                        "execute": true,
-                        "result": "-",
-                        "execution_time": 0.03
-                    },
-                    "median": {
-                        "name": "Median of incoming edges",
-                        "file": "RM005_3_in_edges_median_template.rq",
-                        "replace_dict": {
-                            "{median_position}": "RM005_1_in_edges_total_template.rq"
-                        },
-                        "execute": true,
-                        "result": "-",
-                        "execution_time": 0.03
-                    },
-                    "max": {
-                        "name": "Maximum of incoming edges",
-                        "file": "RM005_4_in_edges_max_template.rq",
-                        "execute": true,
-                        "result": "-",
-                        "execution_time": 0.03
-                    }
+            }
+        },
+        "edges_in": {
+            "name": "Edges - incoming",
+            "files": {
+                "total": {
+                    "name": "Total number of incoming edges",
+                    "file": "RM005_1_in_edges_total_template.rq",
+                    "execute": false
+                },
+                "min": {
+                    "name": "Minimum of incoming edges",
+                    "file": "RM005_2_in_edges_min_template.rq",
+                    "execute": false
+                },
+                "median": {
+                    "name": "Median of incoming edges",
+                    "file": "RM005_3_in_edges_median_template.rq",
+                    "replace_dict": {
+                        "{median_position}": "RM005_1_in_edges_total_template.rq"
+                    },
+                    "execute": false
+                },
+                "max": {
+                    "name": "Maximum of incoming edges",
+                    "file": "RM005_4_in_edges_max_template.rq",
+                    "execute": false
                 }
-            },
-            "connectivity": {
-                "name": "Connectivity",
-                "file": "RM006_connectivity_template.rq",
-                "execute": true,
-                "result": "58909",
-                "execution_time": 2.87
             }
-        }
+        },
+        "connectivity": {
+            "name": "Connectivity",
+            "file": "RM006_connectivity_template.rq",
+            "execute": false
+        },
+        "timestamp": "2025-03-17T10:10",
+        "file": "15_data_service.md"
     },
     "aggregator": {
-        "file": "16_aggregator.md",
-        "uri": "<http://nfdi4earth.de/ontology/Aggregator>",
-        "queries": {
-            "instances": {
-                "name": "Number of Resources",
-                "file": "RM001_instances_template.rq",
-                "execute": true,
-                "result": "38",
-                "execution_time": 0.01
-            },
-            "assertions": {
-                "name": "Number of Assertions",
-                "file": "RM002_assertions_template.rq",
-                "execute": true,
-                "result": "39",
-                "execution_time": 0.01
-            },
-            "linkage": {
-                "name": "Average Linkage",
-                "file": "RM003_linkage_template.rq",
-                "execute": true,
-                "result": "187",
-                "execution_time": 0.02
-            },
-            "edges_out": {
-                "name": "Edges - outgoing",
-                "files": {
-                    "total": {
-                        "name": "Total number of outgoing edges",
-                        "file": "RM004_1_out_edges_total_template.rq",
-                        "execute": true,
-                        "result": "38",
-                        "execution_time": 0.01
-                    },
-                    "min": {
-                        "name": "Minimum of outgoing edges",
-                        "file": "RM004_2_out_edges_min_template.rq",
-                        "execute": true,
-                        "result": "8",
-                        "execution_time": 0.01
-                    },
-                    "median": {
-                        "name": "Median of outgoing edges",
-                        "file": "RM004_3_out_edges_median_template.rq",
-                        "replace_dict": {
-                            "{median_position}": "RM004_1_out_edges_total_template.rq"
-                        },
-                        "execute": true,
-                        "result": "36",
-                        "execution_time": 0.01
-                    },
-                    "max": {
-                        "name": "Maximum of outgoing edges",
-                        "file": "RM004_4_out_edges_max_template.rq",
-                        "execute": true,
-                        "result": "57",
-                        "execution_time": 0.01
-                    }
+        "instances": {
+            "name": "Number of Resources",
+            "file": "RM001_instances_template.rq",
+            "execute": true,
+            "result": "38",
+            "execution_time": 0.01
+        },
+        "assertions": {
+            "name": "Number of Assertions",
+            "file": "RM002_assertions_template.rq",
+            "execute": true,
+            "result": "39",
+            "execution_time": 0.01
+        },
+        "linkage": {
+            "name": "Average Linkage",
+            "file": "RM003_linkage_template.rq",
+            "execute": false
+        },
+        "edges_out": {
+            "name": "Edges - outgoing",
+            "files": {
+                "total": {
+                    "name": "Total number of outgoing edges",
+                    "file": "RM004_1_out_edges_total_template.rq",
+                    "execute": false
+                },
+                "min": {
+                    "name": "Minimum of outgoing edges",
+                    "file": "RM004_2_out_edges_min_template.rq",
+                    "execute": false
+                },
+                "median": {
+                    "name": "Median of outgoing edges",
+                    "file": "RM004_3_out_edges_median_template.rq",
+                    "replace_dict": {
+                        "{median_position}": "RM004_1_out_edges_total_template.rq"
+                    },
+                    "execute": false
+                },
+                "max": {
+                    "name": "Maximum of outgoing edges",
+                    "file": "RM004_4_out_edges_max_template.rq",
+                    "execute": false
                 }
-            },
-            "edges_in": {
-                "name": "Edges - incoming",
-                "files": {
-                    "total": {
-                        "name": "Total number of incoming edges",
-                        "file": "RM005_1_in_edges_total_template.rq",
-                        "execute": true,
-                        "result": "1",
-                        "execution_time": 0.01
-                    },
-                    "min": {
-                        "name": "Minimum of incoming edges",
-                        "file": "RM005_2_in_edges_min_template.rq",
-                        "execute": true,
-                        "result": "151",
-                        "execution_time": 1.04
-                    },
-                    "median": {
-                        "name": "Median of incoming edges",
-                        "file": "RM005_3_in_edges_median_template.rq",
-                        "replace_dict": {
-                            "{median_position}": "RM005_1_in_edges_total_template.rq"
-                        },
-                        "execute": true,
-                        "result": "151",
-                        "execution_time": 1.01
-                    },
-                    "max": {
-                        "name": "Maximum of incoming edges",
-                        "file": "RM005_4_in_edges_max_template.rq",
-                        "execute": true,
-                        "result": "151",
-                        "execution_time": 0.01
-                    }
+            }
+        },
+        "edges_in": {
+            "name": "Edges - incoming",
+            "files": {
+                "total": {
+                    "name": "Total number of incoming edges",
+                    "file": "RM005_1_in_edges_total_template.rq",
+                    "execute": false
+                },
+                "min": {
+                    "name": "Minimum of incoming edges",
+                    "file": "RM005_2_in_edges_min_template.rq",
+                    "execute": false
+                },
+                "median": {
+                    "name": "Median of incoming edges",
+                    "file": "RM005_3_in_edges_median_template.rq",
+                    "replace_dict": {
+                        "{median_position}": "RM005_1_in_edges_total_template.rq"
+                    },
+                    "execute": false
+                },
+                "max": {
+                    "name": "Maximum of incoming edges",
+                    "file": "RM005_4_in_edges_max_template.rq",
+                    "execute": false
                 }
-            },
-            "connectivity": {
-                "name": "Connectivity",
-                "file": "RM006_connectivity_template.rq",
-                "execute": true,
-                "result": "239",
-                "execution_time": 0.01
             }
-        }
+        },
+        "connectivity": {
+            "name": "Connectivity",
+            "file": "RM006_connectivity_template.rq",
+            "execute": false
+        },
+        "timestamp": "2025-03-17T10:10",
+        "file": "16_aggregator.md"
     },
     "person": {
-        "file": "17_person.md",
-        "uri": "<http://schema.org/Person>",
-        "queries": {
-            "instances": {
-                "name": "Number of Resources",
-                "file": "RM001_instances_template.rq",
-                "execute": true,
-                "result": "2180",
-                "execution_time": 0.01
-            },
-            "assertions": {
-                "name": "Number of Assertions",
-                "file": "RM002_assertions_template.rq",
-                "execute": true,
-                "result": "2181",
-                "execution_time": 0.01
-            },
-            "linkage": {
-                "name": "Average Linkage",
-                "file": "RM003_linkage_template.rq",
-                "execute": true,
-                "result": "4.628269848554383",
-                "execution_time": 0.02
-            },
-            "edges_out": {
-                "name": "Edges - outgoing",
-                "files": {
-                    "total": {
-                        "name": "Total number of outgoing edges",
-                        "file": "RM004_1_out_edges_total_template.rq",
-                        "execute": true,
-                        "result": "2180",
-                        "execution_time": 0.01
-                    },
-                    "min": {
-                        "name": "Minimum of outgoing edges",
-                        "file": "RM004_2_out_edges_min_template.rq",
-                        "execute": true,
-                        "result": "2",
-                        "execution_time": 0.01
-                    },
-                    "median": {
-                        "name": "Median of outgoing edges",
-                        "file": "RM004_3_out_edges_median_template.rq",
-                        "replace_dict": {
-                            "{median_position}": "RM004_1_out_edges_total_template.rq"
-                        },
-                        "execute": true,
-                        "result": "4",
-                        "execution_time": 1.05
-                    },
-                    "max": {
-                        "name": "Maximum of outgoing edges",
-                        "file": "RM004_4_out_edges_max_template.rq",
-                        "execute": true,
-                        "result": "7",
-                        "execution_time": 0.01
-                    }
+        "instances": {
+            "name": "Number of Resources",
+            "file": "RM001_instances_template.rq",
+            "execute": true,
+            "result": "2180",
+            "execution_time": 0.01
+        },
+        "assertions": {
+            "name": "Number of Assertions",
+            "file": "RM002_assertions_template.rq",
+            "execute": true,
+            "result": "2181",
+            "execution_time": 0.01
+        },
+        "linkage": {
+            "name": "Average Linkage",
+            "file": "RM003_linkage_template.rq",
+            "execute": false
+        },
+        "edges_out": {
+            "name": "Edges - outgoing",
+            "files": {
+                "total": {
+                    "name": "Total number of outgoing edges",
+                    "file": "RM004_1_out_edges_total_template.rq",
+                    "execute": false
+                },
+                "min": {
+                    "name": "Minimum of outgoing edges",
+                    "file": "RM004_2_out_edges_min_template.rq",
+                    "execute": false
+                },
+                "median": {
+                    "name": "Median of outgoing edges",
+                    "file": "RM004_3_out_edges_median_template.rq",
+                    "replace_dict": {
+                        "{median_position}": "RM004_1_out_edges_total_template.rq"
+                    },
+                    "execute": false
+                },
+                "max": {
+                    "name": "Maximum of outgoing edges",
+                    "file": "RM004_4_out_edges_max_template.rq",
+                    "execute": false
                 }
-            },
-            "edges_in": {
-                "name": "Edges - incoming",
-                "files": {
-                    "total": {
-                        "name": "Total number of incoming edges",
-                        "file": "RM005_1_in_edges_total_template.rq",
-                        "execute": true,
-                        "result": "2179",
-                        "execution_time": 0.01
-                    },
-                    "min": {
-                        "name": "Minimum of incoming edges",
-                        "file": "RM005_2_in_edges_min_template.rq",
-                        "execute": true,
-                        "result": "1",
-                        "execution_time": 0.01
-                    },
-                    "median": {
-                        "name": "Median of incoming edges",
-                        "file": "RM005_3_in_edges_median_template.rq",
-                        "replace_dict": {
-                            "{median_position}": "RM005_1_in_edges_total_template.rq"
-                        },
-                        "execute": true,
-                        "result": "1",
-                        "execution_time": 0.02
-                    },
-                    "max": {
-                        "name": "Maximum of incoming edges",
-                        "file": "RM005_4_in_edges_max_template.rq",
-                        "execute": true,
-                        "result": "2",
-                        "execution_time": 0.01
-                    }
+            }
+        },
+        "edges_in": {
+            "name": "Edges - incoming",
+            "files": {
+                "total": {
+                    "name": "Total number of incoming edges",
+                    "file": "RM005_1_in_edges_total_template.rq",
+                    "execute": false
+                },
+                "min": {
+                    "name": "Minimum of incoming edges",
+                    "file": "RM005_2_in_edges_min_template.rq",
+                    "execute": false
+                },
+                "median": {
+                    "name": "Median of incoming edges",
+                    "file": "RM005_3_in_edges_median_template.rq",
+                    "replace_dict": {
+                        "{median_position}": "RM005_1_in_edges_total_template.rq"
+                    },
+                    "execute": false
+                },
+                "max": {
+                    "name": "Maximum of incoming edges",
+                    "file": "RM005_4_in_edges_max_template.rq",
+                    "execute": false
                 }
-            },
-            "connectivity": {
-                "name": "Connectivity",
-                "file": "RM006_connectivity_template.rq",
-                "execute": true,
-                "result": "2",
-                "execution_time": 0.01
             }
-        }
+        },
+        "connectivity": {
+            "name": "Connectivity",
+            "file": "RM006_connectivity_template.rq",
+            "execute": false
+        },
+        "timestamp": "2025-03-17T10:10",
+        "file": "17_person.md"
     },
     "registry": {
-        "file": "18_registry.md",
-        "uri": "<http://nfdi4earth.de/ontology/Registry>",
-        "queries": {
-            "instances": {
-                "name": "Number of Resources",
-                "file": "RM001_instances_template.rq",
-                "execute": true,
-                "result": "5",
-                "execution_time": 0.01
-            },
-            "assertions": {
-                "name": "Number of Assertions",
-                "file": "RM002_assertions_template.rq",
-                "execute": true,
-                "result": "6",
-                "execution_time": 0.01
-            },
-            "linkage": {
-                "name": "Average Linkage",
-                "file": "RM003_linkage_template.rq",
-                "execute": true,
-                "result": 0,
-                "execution_time": 0.01
-            },
-            "edges_out": {
-                "name": "Edges - outgoing",
-                "files": {
-                    "total": {
-                        "name": "Total number of outgoing edges",
-                        "file": "RM004_1_out_edges_total_template.rq",
-                        "execute": true,
-                        "result": "5",
-                        "execution_time": 0.01
-                    },
-                    "min": {
-                        "name": "Minimum of outgoing edges",
-                        "file": "RM004_2_out_edges_min_template.rq",
-                        "execute": true,
-                        "result": "7",
-                        "execution_time": 0.01
-                    },
-                    "median": {
-                        "name": "Median of outgoing edges",
-                        "file": "RM004_3_out_edges_median_template.rq",
-                        "replace_dict": {
-                            "{median_position}": "RM004_1_out_edges_total_template.rq"
-                        },
-                        "execute": true,
-                        "result": "9",
-                        "execution_time": 0.01
-                    },
-                    "max": {
-                        "name": "Maximum of outgoing edges",
-                        "file": "RM004_4_out_edges_max_template.rq",
-                        "execute": true,
-                        "result": "11",
-                        "execution_time": 0.01
-                    }
+        "instances": {
+            "name": "Number of Resources",
+            "file": "RM001_instances_template.rq",
+            "execute": true,
+            "result": "5",
+            "execution_time": 0.01
+        },
+        "assertions": {
+            "name": "Number of Assertions",
+            "file": "RM002_assertions_template.rq",
+            "execute": true,
+            "result": "6",
+            "execution_time": 0.01
+        },
+        "linkage": {
+            "name": "Average Linkage",
+            "file": "RM003_linkage_template.rq",
+            "execute": false
+        },
+        "edges_out": {
+            "name": "Edges - outgoing",
+            "files": {
+                "total": {
+                    "name": "Total number of outgoing edges",
+                    "file": "RM004_1_out_edges_total_template.rq",
+                    "execute": false
+                },
+                "min": {
+                    "name": "Minimum of outgoing edges",
+                    "file": "RM004_2_out_edges_min_template.rq",
+                    "execute": false
+                },
+                "median": {
+                    "name": "Median of outgoing edges",
+                    "file": "RM004_3_out_edges_median_template.rq",
+                    "replace_dict": {
+                        "{median_position}": "RM004_1_out_edges_total_template.rq"
+                    },
+                    "execute": false
+                },
+                "max": {
+                    "name": "Maximum of outgoing edges",
+                    "file": "RM004_4_out_edges_max_template.rq",
+                    "execute": false
                 }
-            },
-            "edges_in": {
-                "name": "Edges - incoming",
-                "files": {
-                    "total": {
-                        "name": "Total number of incoming edges",
-                        "file": "RM005_1_in_edges_total_template.rq",
-                        "execute": true,
-                        "result": "0",
-                        "execution_time": 0.01
-                    },
-                    "min": {
-                        "name": "Minimum of incoming edges",
-                        "file": "RM005_2_in_edges_min_template.rq",
-                        "execute": true,
-                        "result": "-",
-                        "execution_time": 0.01
-                    },
-                    "median": {
-                        "name": "Median of incoming edges",
-                        "file": "RM005_3_in_edges_median_template.rq",
-                        "replace_dict": {
-                            "{median_position}": "RM005_1_in_edges_total_template.rq"
-                        },
-                        "execute": true,
-                        "result": "-",
-                        "execution_time": 0.01
-                    },
-                    "max": {
-                        "name": "Maximum of incoming edges",
-                        "file": "RM005_4_in_edges_max_template.rq",
-                        "execute": true,
-                        "result": "-",
-                        "execution_time": 0.01
-                    }
+            }
+        },
+        "edges_in": {
+            "name": "Edges - incoming",
+            "files": {
+                "total": {
+                    "name": "Total number of incoming edges",
+                    "file": "RM005_1_in_edges_total_template.rq",
+                    "execute": false
+                },
+                "min": {
+                    "name": "Minimum of incoming edges",
+                    "file": "RM005_2_in_edges_min_template.rq",
+                    "execute": false
+                },
+                "median": {
+                    "name": "Median of incoming edges",
+                    "file": "RM005_3_in_edges_median_template.rq",
+                    "replace_dict": {
+                        "{median_position}": "RM005_1_in_edges_total_template.rq"
+                    },
+                    "execute": false
+                },
+                "max": {
+                    "name": "Maximum of incoming edges",
+                    "file": "RM005_4_in_edges_max_template.rq",
+                    "execute": false
                 }
-            },
-            "connectivity": {
-                "name": "Connectivity",
-                "file": "RM006_connectivity_template.rq",
-                "execute": true,
-                "result": "15",
-                "execution_time": 0.01
             }
-        }
+        },
+        "connectivity": {
+            "name": "Connectivity",
+            "file": "RM006_connectivity_template.rq",
+            "execute": false
+        },
+        "timestamp": "2025-03-17T10:10",
+        "file": "18_registry.md"
     },
-    "timestamp": "2025-03-14T16:21"
+    "timestamp": "2025-03-17T10:10"
 }
\ No newline at end of file
diff --git a/scripts/kg_analysis/metrics_runner.py b/scripts/kg_analysis/metrics_runner.py
index 65b0d35..71f0ad4 100644
--- a/scripts/kg_analysis/metrics_runner.py
+++ b/scripts/kg_analysis/metrics_runner.py
@@ -5,7 +5,7 @@ import logging
 
 import json
 
-from abc import ABC, abstractmethod
+from abc import ABC
 from copy import deepcopy
 from datetime import datetime
 from pathlib import Path
@@ -76,11 +76,6 @@ class MetricsRunnerBase(ABC):
             return list(result["results"]["bindings"][0].values())[0]["value"]
         return "-"
 
-    @abstractmethod
-    def run(self) -> dict:
-        """Run this specific metric."""
-        pass
-
     def save_report(self, result) -> None:
         """Run a metric query and optionally save the results."""
 
@@ -114,98 +109,172 @@ class MetricsRunnerBase(ABC):
         ) as f:
             json.dump(dictionary, f, indent=4, ensure_ascii=False)
 
+    def _get_result(self, query, resource_type, replace_dict=None):
+        if not query["execute"]:
+            return
+        log.info(f"Metric: {query['name']} ({query['file']})")
+        _replace_dict = {
+            "{resource_type}": resource_type,
+        }
+        if replace_dict:
+            _replace_dict.update(replace_dict)
+        result = self.query_metric(
+            self.get_query_path(query["file"]),
+            replace_dict=_replace_dict,
+        )
+        query["result"] = result
+        query["execution_time"] = round(self._execution_time, 2)
 
-class MetricsRunner_001(MetricsRunnerBase):
-    """Runner for Metric 001: Instance Count"""
+    def run(self, resource_type_uri=None):
+        """
+        Execute metrics analysis and save results to JSON.
 
-    _query_file = "GM001.rq"
-    _output_file = "GM001.txt"
+        Args:
+            resource_type_uri (str, optional): URI of specific resource type.
+                                            If None, runs general metrics.
+        Returns:
+            dict: Results of the metric analysis
+        """
+        # Create deep copy of query templates
+        queries = deepcopy(self.query_templates)
 
-    def run(self):
-        result = self.query_metric(self.query_path)
-        result_text = f"- Instance count: {result}"
-        self.save(result_text)
+        # Process each query
+        for query_key, query in queries.items():
+            if query_key == "timestamp":
+                continue
 
+            if "files" in query:
+                # Handle composite queries (edges)
+                for metric, sub_query in query["files"].items():
+                    replace_dict = {}
 
-class MetricsRunner_002(MetricsRunnerBase):
-    """Runner for Metric 002: Assertions Count"""
+                    # Handle queries that need pre-calculated values
+                    if "replace_dict" in sub_query and sub_query["execute"]:
+                        for dict_element, dependency_query in sub_query[
+                            "replace_dict"
+                        ].items():
+                            # Get total count for calculations (e.g. median)
+                            total = self.query_metric(
+                                self.get_query_path(dependency_query),
+                                replace_dict=(
+                                    {"{resource_type}": resource_type_uri}
+                                    if resource_type_uri
+                                    else None
+                                ),
+                            )
+                            try:
+                                total = int(total)
+                            except ValueError:
+                                log.error(
+                                    f"Failed to convert {total} to integer"
+                                )
+                                continue
 
-    _query_file = "GM002_1.rq"
-    _output_file = "GM002.txt"
+                            if total and metric == "median":
+                                # Special handling for median calculations
+                                replace_dict[dict_element] = int(total) / 2
+                            else:
+                                replace_dict[dict_element] = total
 
-    def run(self):
-        result = self.query_metric(self.query_path)
-        result_text = f"- Assertions count: {result}"
-        self.save(result_text)
+                    # Execute the sub-query
+                    self._get_result(
+                        sub_query,
+                        resource_type_uri if resource_type_uri else None,
+                        replace_dict=replace_dict,
+                    )
+            else:
+                # Execute simple queries
+                self._get_result(
+                    query, resource_type_uri if resource_type_uri else None
+                )
 
+        # Add timestamp
+        queries["timestamp"] = datetime.now().isoformat(timespec="minutes")
 
-class MetricsRunner_003(MetricsRunnerBase):
-    """Runner for Metric 003: Average Linkage Degree"""
+        return queries
 
-    _query_file = "GM003_1.rq"
-    _output_file = "GM003.txt"
+
+class MetricsRunner_General(MetricsRunnerBase):
+    """Runner for General Metrics (GM001-GM005)"""
 
     def run(self):
-        result = self.query_metric(self.query_path)
-        result_text = f"- Assertions count: {result}"
-        self.save(result_text)
+        output_path = "reports/metrics/general.json"
+        queries = super().run()
 
+        self.save_to_json(queries, output_path)
 
-class MetricsRunner_Edges(MetricsRunnerBase):
-    """Runner for Metric 004 & 005: Outgoing & Incoming Edges"""
+        return queries
 
-    _files = {
-        "outgoing": {
-            "outfile": "GM004.txt",
-            "total_query": "GM004_2.rq",
-            "median_query": "GM004_4.rq",
-            "min_query": "GM004_5.rq",
-            "max_query": "GM004_6.rq",
+    query_templates = {
+        "instances": {
+            "name": "Instance Count",
+            "file": "GM001.rq",
+            "execute": True,
+        },
+        "assertions": {
+            "name": "Assertions Count",
+            "file": "GM002_1.rq",
+            "execute": False,
         },
-        "incoming": {
-            "outfile": "GM005.txt",
-            "total_query": "GM005_2.rq",
-            "median_query": "GM005_4.rq",
-            "min_query": "GM005_5.rq",
-            "max_query": "GM005_6.rq",
+        "linkage": {
+            "name": "Average Linkage Degree",
+            "file": "GM003_2.rq",
+            "execute": False,
+        },
+        "edges_out": {
+            "name": "Edges - outgoing",
+            "files": {
+                "total": {
+                    "name": "Total number of outgoing edges",
+                    "file": "GM004_2.rq",
+                    "execute": False,
+                },
+                "min": {
+                    "name": "Minimum of outgoing edges",
+                    "file": "GM004_5.rq",
+                    "execute": False,
+                },
+                "median": {
+                    "name": "Median of outgoing edges",
+                    "file": "GM004_4.rq",
+                    "replace_dict": {"{median_position}": "GM004_2.rq"},
+                    "execute": False,
+                },
+                "max": {
+                    "name": "Maximum of outgoing edges",
+                    "file": "GM004_6.rq",
+                    "execute": False,
+                },
+            },
+        },
+        "edges_in": {
+            "name": "Edges - incoming",
+            "files": {
+                "total": {
+                    "name": "Total number of incoming edges",
+                    "file": "GM005_2.rq",
+                    "execute": False,
+                },
+                "min": {
+                    "name": "Minimum of incoming edges",
+                    "file": "GM005_5.rq",
+                    "execute": False,
+                },
+                "median": {
+                    "name": "Median of incoming edges",
+                    "file": "GM005_4.rq",
+                    "replace_dict": {"{median_position}": "GM005_2.rq"},
+                    "execute": False,
+                },
+                "max": {
+                    "name": "Maximum of incoming edges",
+                    "file": "GM005_6.rq",
+                    "execute": False,
+                },
+            },
         },
     }
 
-    def __init__(self, _type: str, *args, **kwargs):
-        super().__init__(*args, **kwargs)
-        self.type = _type
-        self._output_file = self._files[_type]["outfile"]
-
-    def run(self):
-        edges_total = self.query_metric(
-            self.get_query_path(self._files[self.type]["total_query"])
-        )
-        minimum = self.query_metric(
-            self.get_query_path(self._files[self.type]["min_query"])
-        )
-        maximum = self.query_metric(
-            self.get_query_path(self._files[self.type]["max_query"])
-        )
-
-        if not edges_total:
-            self.fail = True
-            return
-
-        edges_total = int(edges_total)
-        if edges_total % 2:
-            log.warning("Odd number of edges, median is not unique")
-        median_position = int(edges_total / 2)
-        median = self.query_metric(
-            self.get_query_path(self._files[self.type]["median_query"]),
-            replace_dict={"{median_position}": median_position},
-        )
-
-        result_text = f"- Total count: {edges_total}"
-        result_text += f"\n- Minimum: {minimum}"
-        result_text += f"\n- Median: {median}"
-        result_text += f"\n- Maximum: {maximum}"
-        self.save(result_text)
-
 
 class MetricsRunner_Resources(MetricsRunnerBase):
     query_templates = {
@@ -222,7 +291,7 @@ class MetricsRunner_Resources(MetricsRunnerBase):
         "linkage": {
             "name": "Average Linkage",
             "file": "RM003_linkage_template.rq",
-            "execute": True,
+            "execute": False,
         },
         "edges_out": {
             "name": "Edges - outgoing",
@@ -230,12 +299,12 @@ class MetricsRunner_Resources(MetricsRunnerBase):
                 "total": {
                     "name": "Total number of outgoing edges",
                     "file": "RM004_1_out_edges_total_template.rq",
-                    "execute": True,
+                    "execute": False,
                 },
                 "min": {
                     "name": "Minimum of outgoing edges",
                     "file": "RM004_2_out_edges_min_template.rq",
-                    "execute": True,
+                    "execute": False,
                 },
                 "median": {
                     "name": "Median of outgoing edges",
@@ -243,12 +312,12 @@ class MetricsRunner_Resources(MetricsRunnerBase):
                     "replace_dict": {
                         "{median_position}": "RM004_1_out_edges_total_template.rq"
                     },
-                    "execute": True,
+                    "execute": False,
                 },
                 "max": {
                     "name": "Maximum of outgoing edges",
                     "file": "RM004_4_out_edges_max_template.rq",
-                    "execute": True,
+                    "execute": False,
                 },
             },
         },
@@ -258,12 +327,12 @@ class MetricsRunner_Resources(MetricsRunnerBase):
                 "total": {
                     "name": "Total number of incoming edges",
                     "file": "RM005_1_in_edges_total_template.rq",
-                    "execute": True,
+                    "execute": False,
                 },
                 "min": {
                     "name": "Minimum of incoming edges",
                     "file": "RM005_2_in_edges_min_template.rq",
-                    "execute": True,
+                    "execute": False,
                 },
                 "median": {
                     "name": "Median of incoming edges",
@@ -271,19 +340,19 @@ class MetricsRunner_Resources(MetricsRunnerBase):
                     "replace_dict": {
                         "{median_position}": "RM005_1_in_edges_total_template.rq"
                     },
-                    "execute": True,
+                    "execute": False,
                 },
                 "max": {
                     "name": "Maximum of incoming edges",
                     "file": "RM005_4_in_edges_max_template.rq",
-                    "execute": True,
+                    "execute": False,
                 },
             },
         },
         "connectivity": {
             "name": "Connectivity",
             "file": "RM006_connectivity_template.rq",
-            "execute": True,
+            "execute": False,
         },
     }
 
@@ -342,100 +411,26 @@ class MetricsRunner_Resources(MetricsRunnerBase):
         },
     }
 
-    def _get_result(self, query, resource_type, replace_dict=None):
-        log.info(f"Metric: {query['name']} ({query['file']})")
-        if query["execute"] is False:
-            return
-        _replace_dict = {
-            "{resource_type}": resource_type,
-        }
-        if replace_dict:
-            _replace_dict.update(replace_dict)
-        result = self.query_metric(
-            self.get_query_path(query["file"]),
-            replace_dict=_replace_dict,
-        )
-        query["result"] = result
-        query["execution_time"] = round(self._execution_time, 2)
-
-
-def run(self):
-    """
-    Execute metrics analysis for all resource types and save results to JSON.
-    This method:
-    1. Iterates through all resource types
-    2. Executes queries for each metric
-    3. Handles complex metrics with sub-queries
-    4. Saves results to a JSON file
-    """
-    # Iterate through each resource type (dataset, publication, etc.)
-    for resource_type, data in self.resource_types.items():
-        log.info(f"Analyzing: {data['uri']}")
-        # Create a deep copy of query templates to avoid modifying the original
-        queries = deepcopy(self.query_templates)
-
-        # Process each query for the current resource type
-        for query in queries.values():
-            # Check if this is a composite query with sub-queries
-            if "files" in query:
-                # Process each sub-query (e.g., total, min, median, max)
-                for metric, sub_query in query["files"].items():
-                    # Initialize dictionary for replacement values
-                    replace_dict = {}
-
-                    # Handle queries that need pre-calculated values
-                    if "replace_dict" in sub_query:
-                        # Process each replacement needed for this query
-                        for dict_element, sub_sub_query in sub_query[
-                            "replace_dict"
-                        ].items():
-                            # Execute the sub-query to get the replacement value
-                            replace_dict[dict_element] = self.query_metric(
-                                self.get_query_path(sub_sub_query),
-                                replace_dict={
-                                    "{resource_type}": data["uri"],
-                                },
-                            )
-
-                            # Special handling for median calculations
-                            # If this is a median metric, divide the result by 2
-                            if (
-                                replace_dict[dict_element]
-                                and metric == "median"
-                            ):
-                                replace_dict[dict_element] = (
-                                    int(replace_dict[dict_element]) / 2
-                                )
-
-                    # Execute the sub-query with all necessary replacements
-                    self._get_result(
-                        sub_query, data["uri"], replace_dict=replace_dict
-                    )
-            else:
-                # Execute simple queries directly
-                self._get_result(query, data["uri"])
-
-        # Store the query results for this resource type
-        self.resource_types[resource_type]["queries"] = queries
-
-    # Add timestamp to track when the metrics were last updated
-    self.resource_types["timestamp"] = datetime.now().isoformat(
-        timespec="minutes"
-    )
+    def run(self):
+        """Run metrics for all resource types"""
+        results = {}
+        for resource_type, data in self.resource_types.items():
+            results[resource_type] = super().run(data["uri"])
+            results[resource_type]["file"] = data["file"]
 
-    # Save all results to JSON file
-    self.save_to_json(self.resource_types, "reports/metrics/resources.json")
+        results["timestamp"] = datetime.now().isoformat(timespec="minutes")
+        self.save_to_json(results, "reports/metrics/resources.json")
+        return results
 
 
 class MetricsRunner(ABC):
 
     def run(self):
-        # MetricsRunner_001().run()
-        # MetricsRunner_002().run()
-        # MetricsRunner_003().run()
-        # MetricsRunner_Edges("outgoing").run()
-        # MetricsRunner_Edges("incoming").run()
+        """Run all metrics analysis"""
+        # Run general metrics
+        mg = MetricsRunner_General()
+        mg.run()
+
+        # Run resource-specific metrics
         mr = MetricsRunner_Resources()
         mr.run()
-        # mr.export_report_to_table()
-        # rprint(mr.resource_types)
-- 
GitLab


From 2849f92501d2940bd00faa9f6580340e0f07adc3 Mon Sep 17 00:00:00 2001
From: Ralf Klammer <ralf.klammer@tu-dresden.de>
Date: Mon, 17 Mar 2025 14:34:42 +0100
Subject: [PATCH 20/59] [metrics] Move configuration to `interfaces/*`

---
 reports/metrics/resources.json              | 512 ++++++++++++++------
 scripts/kg_analysis/interfaces/__init__.py  |   3 +
 scripts/kg_analysis/interfaces/general.py   |  76 +++
 scripts/kg_analysis/interfaces/resources.py | 140 ++++++
 scripts/kg_analysis/metrics_runner.py       | 312 +++---------
 5 files changed, 666 insertions(+), 377 deletions(-)
 create mode 100644 scripts/kg_analysis/interfaces/__init__.py
 create mode 100644 scripts/kg_analysis/interfaces/general.py
 create mode 100644 scripts/kg_analysis/interfaces/resources.py

diff --git a/reports/metrics/resources.json b/reports/metrics/resources.json
index ca20225..f3a8d17 100644
--- a/reports/metrics/resources.json
+++ b/reports/metrics/resources.json
@@ -17,7 +17,9 @@
         "linkage": {
             "name": "Average Linkage",
             "file": "RM003_linkage_template.rq",
-            "execute": false
+            "execute": true,
+            "result": "68",
+            "execution_time": 0.07
         },
         "edges_out": {
             "name": "Edges - outgoing",
@@ -25,12 +27,16 @@
                 "total": {
                     "name": "Total number of outgoing edges",
                     "file": "RM004_1_out_edges_total_template.rq",
-                    "execute": false
+                    "execute": true,
+                    "result": "724903",
+                    "execution_time": 0.01
                 },
                 "min": {
                     "name": "Minimum of outgoing edges",
                     "file": "RM004_2_out_edges_min_template.rq",
-                    "execute": false
+                    "execute": true,
+                    "result": "6",
+                    "execution_time": 0.01
                 },
                 "median": {
                     "name": "Median of outgoing edges",
@@ -38,12 +44,16 @@
                     "replace_dict": {
                         "{median_position}": "RM004_1_out_edges_total_template.rq"
                     },
-                    "execute": false
+                    "execute": true,
+                    "result": "23",
+                    "execution_time": 0.01
                 },
                 "max": {
                     "name": "Maximum of outgoing edges",
                     "file": "RM004_4_out_edges_max_template.rq",
-                    "execute": false
+                    "execute": true,
+                    "result": "12519",
+                    "execution_time": 0.01
                 }
             }
         },
@@ -53,12 +63,16 @@
                 "total": {
                     "name": "Total number of incoming edges",
                     "file": "RM005_1_in_edges_total_template.rq",
-                    "execute": false
+                    "execute": true,
+                    "result": "4",
+                    "execution_time": 0.12
                 },
                 "min": {
                     "name": "Minimum of incoming edges",
                     "file": "RM005_2_in_edges_min_template.rq",
-                    "execute": false
+                    "execute": true,
+                    "result": "1",
+                    "execution_time": 0.04
                 },
                 "median": {
                     "name": "Median of incoming edges",
@@ -66,21 +80,27 @@
                     "replace_dict": {
                         "{median_position}": "RM005_1_in_edges_total_template.rq"
                     },
-                    "execute": false
+                    "execute": true,
+                    "result": "1",
+                    "execution_time": 1.11
                 },
                 "max": {
                     "name": "Maximum of incoming edges",
                     "file": "RM005_4_in_edges_max_template.rq",
-                    "execute": false
+                    "execute": true,
+                    "result": "107",
+                    "execution_time": 0.05
                 }
             }
         },
         "connectivity": {
             "name": "Connectivity",
             "file": "RM006_connectivity_template.rq",
-            "execute": false
+            "execute": true,
+            "result": "427594",
+            "execution_time": 0.01
         },
-        "timestamp": "2025-03-17T10:10",
+        "timestamp": "2025-03-17T13:46",
         "file": "06_dataset.md"
     },
     "publication": {
@@ -96,12 +116,14 @@
             "file": "RM002_assertions_template.rq",
             "execute": true,
             "result": "6",
-            "execution_time": 0.01
+            "execution_time": 1.05
         },
         "linkage": {
             "name": "Average Linkage",
             "file": "RM003_linkage_template.rq",
-            "execute": false
+            "execute": true,
+            "result": 0,
+            "execution_time": 0.02
         },
         "edges_out": {
             "name": "Edges - outgoing",
@@ -109,12 +131,16 @@
                 "total": {
                     "name": "Total number of outgoing edges",
                     "file": "RM004_1_out_edges_total_template.rq",
-                    "execute": false
+                    "execute": true,
+                    "result": "5",
+                    "execution_time": 0.01
                 },
                 "min": {
                     "name": "Minimum of outgoing edges",
                     "file": "RM004_2_out_edges_min_template.rq",
-                    "execute": false
+                    "execute": true,
+                    "result": "7",
+                    "execution_time": 0.01
                 },
                 "median": {
                     "name": "Median of outgoing edges",
@@ -122,12 +148,16 @@
                     "replace_dict": {
                         "{median_position}": "RM004_1_out_edges_total_template.rq"
                     },
-                    "execute": false
+                    "execute": true,
+                    "result": "9",
+                    "execution_time": 1.03
                 },
                 "max": {
                     "name": "Maximum of outgoing edges",
                     "file": "RM004_4_out_edges_max_template.rq",
-                    "execute": false
+                    "execute": true,
+                    "result": "11",
+                    "execution_time": 0.02
                 }
             }
         },
@@ -137,12 +167,16 @@
                 "total": {
                     "name": "Total number of incoming edges",
                     "file": "RM005_1_in_edges_total_template.rq",
-                    "execute": false
+                    "execute": true,
+                    "result": "0",
+                    "execution_time": 0.02
                 },
                 "min": {
                     "name": "Minimum of incoming edges",
                     "file": "RM005_2_in_edges_min_template.rq",
-                    "execute": false
+                    "execute": true,
+                    "result": null,
+                    "execution_time": 0.01
                 },
                 "median": {
                     "name": "Median of incoming edges",
@@ -150,21 +184,27 @@
                     "replace_dict": {
                         "{median_position}": "RM005_1_in_edges_total_template.rq"
                     },
-                    "execute": false
+                    "execute": true,
+                    "result": null,
+                    "execution_time": 0.01
                 },
                 "max": {
                     "name": "Maximum of incoming edges",
                     "file": "RM005_4_in_edges_max_template.rq",
-                    "execute": false
+                    "execute": true,
+                    "result": null,
+                    "execution_time": 0.01
                 }
             }
         },
         "connectivity": {
             "name": "Connectivity",
             "file": "RM006_connectivity_template.rq",
-            "execute": false
+            "execute": true,
+            "result": "15",
+            "execution_time": 0.01
         },
-        "timestamp": "2025-03-17T10:10",
+        "timestamp": "2025-03-17T13:46",
         "file": "07_publication.md"
     },
     "learning_resource": {
@@ -185,7 +225,9 @@
         "linkage": {
             "name": "Average Linkage",
             "file": "RM003_linkage_template.rq",
-            "execute": false
+            "execute": true,
+            "result": "25",
+            "execution_time": 0.02
         },
         "edges_out": {
             "name": "Edges - outgoing",
@@ -193,12 +235,16 @@
                 "total": {
                     "name": "Total number of outgoing edges",
                     "file": "RM004_1_out_edges_total_template.rq",
-                    "execute": false
+                    "execute": true,
+                    "result": "512",
+                    "execution_time": 1.02
                 },
                 "min": {
                     "name": "Minimum of outgoing edges",
                     "file": "RM004_2_out_edges_min_template.rq",
-                    "execute": false
+                    "execute": true,
+                    "result": "10",
+                    "execution_time": 0.02
                 },
                 "median": {
                     "name": "Median of outgoing edges",
@@ -206,12 +252,16 @@
                     "replace_dict": {
                         "{median_position}": "RM004_1_out_edges_total_template.rq"
                     },
-                    "execute": false
+                    "execute": true,
+                    "result": "15",
+                    "execution_time": 0.02
                 },
                 "max": {
                     "name": "Maximum of outgoing edges",
                     "file": "RM004_4_out_edges_max_template.rq",
-                    "execute": false
+                    "execute": true,
+                    "result": "27",
+                    "execution_time": 0.01
                 }
             }
         },
@@ -221,12 +271,16 @@
                 "total": {
                     "name": "Total number of incoming edges",
                     "file": "RM005_1_in_edges_total_template.rq",
-                    "execute": false
+                    "execute": true,
+                    "result": "1",
+                    "execution_time": 0.02
                 },
                 "min": {
                     "name": "Minimum of incoming edges",
                     "file": "RM005_2_in_edges_min_template.rq",
-                    "execute": false
+                    "execute": true,
+                    "result": "3",
+                    "execution_time": 0.01
                 },
                 "median": {
                     "name": "Median of incoming edges",
@@ -234,21 +288,27 @@
                     "replace_dict": {
                         "{median_position}": "RM005_1_in_edges_total_template.rq"
                     },
-                    "execute": false
+                    "execute": true,
+                    "result": "3",
+                    "execution_time": 0.01
                 },
                 "max": {
                     "name": "Maximum of incoming edges",
                     "file": "RM005_4_in_edges_max_template.rq",
-                    "execute": false
+                    "execute": true,
+                    "result": "3",
+                    "execution_time": 0.01
                 }
             }
         },
         "connectivity": {
             "name": "Connectivity",
             "file": "RM006_connectivity_template.rq",
-            "execute": false
+            "execute": true,
+            "result": "530",
+            "execution_time": 0.01
         },
-        "timestamp": "2025-03-17T10:10",
+        "timestamp": "2025-03-17T13:46",
         "file": "08_learning_resource.md"
     },
     "repository": {
@@ -269,7 +329,9 @@
         "linkage": {
             "name": "Average Linkage",
             "file": "RM003_linkage_template.rq",
-            "execute": false
+            "execute": true,
+            "result": "90212",
+            "execution_time": 0.02
         },
         "edges_out": {
             "name": "Edges - outgoing",
@@ -277,12 +339,16 @@
                 "total": {
                     "name": "Total number of outgoing edges",
                     "file": "RM004_1_out_edges_total_template.rq",
-                    "execute": false
+                    "execute": true,
+                    "result": "159",
+                    "execution_time": 0.01
                 },
                 "min": {
                     "name": "Minimum of outgoing edges",
                     "file": "RM004_2_out_edges_min_template.rq",
-                    "execute": false
+                    "execute": true,
+                    "result": "7",
+                    "execution_time": 0.01
                 },
                 "median": {
                     "name": "Median of outgoing edges",
@@ -290,12 +356,16 @@
                     "replace_dict": {
                         "{median_position}": "RM004_1_out_edges_total_template.rq"
                     },
-                    "execute": false
+                    "execute": true,
+                    "result": "44",
+                    "execution_time": 0.01
                 },
                 "max": {
                     "name": "Maximum of outgoing edges",
                     "file": "RM004_4_out_edges_max_template.rq",
-                    "execute": false
+                    "execute": true,
+                    "result": "92",
+                    "execution_time": 0.01
                 }
             }
         },
@@ -305,12 +375,16 @@
                 "total": {
                     "name": "Total number of incoming edges",
                     "file": "RM005_1_in_edges_total_template.rq",
-                    "execute": false
+                    "execute": true,
+                    "result": "5",
+                    "execution_time": 0.06
                 },
                 "min": {
                     "name": "Minimum of incoming edges",
                     "file": "RM005_2_in_edges_min_template.rq",
-                    "execute": false
+                    "execute": true,
+                    "result": "1417",
+                    "execution_time": 0.03
                 },
                 "median": {
                     "name": "Median of incoming edges",
@@ -318,21 +392,27 @@
                     "replace_dict": {
                         "{median_position}": "RM005_1_in_edges_total_template.rq"
                     },
-                    "execute": false
+                    "execute": true,
+                    "result": "6718",
+                    "execution_time": 0.02
                 },
                 "max": {
                     "name": "Maximum of incoming edges",
                     "file": "RM005_4_in_edges_max_template.rq",
-                    "execute": false
+                    "execute": true,
+                    "result": "432941",
+                    "execution_time": 1.09
                 }
             }
         },
         "connectivity": {
             "name": "Connectivity",
             "file": "RM006_connectivity_template.rq",
-            "execute": false
+            "execute": true,
+            "result": "825",
+            "execution_time": 0.01
         },
-        "timestamp": "2025-03-17T10:10",
+        "timestamp": "2025-03-17T13:46",
         "file": "09_repository.md"
     },
     "article_lhb": {
@@ -353,7 +433,9 @@
         "linkage": {
             "name": "Average Linkage",
             "file": "RM003_linkage_template.rq",
-            "execute": false
+            "execute": true,
+            "result": "33.245098039215686",
+            "execution_time": 0.02
         },
         "edges_out": {
             "name": "Edges - outgoing",
@@ -361,12 +443,16 @@
                 "total": {
                     "name": "Total number of outgoing edges",
                     "file": "RM004_1_out_edges_total_template.rq",
-                    "execute": false
+                    "execute": true,
+                    "result": "114",
+                    "execution_time": 0.01
                 },
                 "min": {
                     "name": "Minimum of outgoing edges",
                     "file": "RM004_2_out_edges_min_template.rq",
-                    "execute": false
+                    "execute": true,
+                    "result": "11",
+                    "execution_time": 0.01
                 },
                 "median": {
                     "name": "Median of outgoing edges",
@@ -374,12 +460,16 @@
                     "replace_dict": {
                         "{median_position}": "RM004_1_out_edges_total_template.rq"
                     },
-                    "execute": false
+                    "execute": true,
+                    "result": "30",
+                    "execution_time": 0.01
                 },
                 "max": {
                     "name": "Maximum of outgoing edges",
                     "file": "RM004_4_out_edges_max_template.rq",
-                    "execute": false
+                    "execute": true,
+                    "result": "62",
+                    "execution_time": 0.01
                 }
             }
         },
@@ -389,12 +479,16 @@
                 "total": {
                     "name": "Total number of incoming edges",
                     "file": "RM005_1_in_edges_total_template.rq",
-                    "execute": false
+                    "execute": true,
+                    "result": "102",
+                    "execution_time": 0.02
                 },
                 "min": {
                     "name": "Minimum of incoming edges",
                     "file": "RM005_2_in_edges_min_template.rq",
-                    "execute": false
+                    "execute": true,
+                    "result": "1",
+                    "execution_time": 0.02
                 },
                 "median": {
                     "name": "Median of incoming edges",
@@ -402,21 +496,27 @@
                     "replace_dict": {
                         "{median_position}": "RM005_1_in_edges_total_template.rq"
                     },
-                    "execute": false
+                    "execute": true,
+                    "result": "2",
+                    "execution_time": 0.01
                 },
                 "max": {
                     "name": "Maximum of incoming edges",
                     "file": "RM005_4_in_edges_max_template.rq",
-                    "execute": false
+                    "execute": true,
+                    "result": "24",
+                    "execution_time": 1.08
                 }
             }
         },
         "connectivity": {
             "name": "Connectivity",
             "file": "RM006_connectivity_template.rq",
-            "execute": false
+            "execute": true,
+            "result": "592",
+            "execution_time": 0.01
         },
-        "timestamp": "2025-03-17T10:10",
+        "timestamp": "2025-03-17T13:46",
         "file": "10_article_lhb.md"
     },
     "standards": {
@@ -437,7 +537,9 @@
         "linkage": {
             "name": "Average Linkage",
             "file": "RM003_linkage_template.rq",
-            "execute": false
+            "execute": true,
+            "result": "13.714285714285714",
+            "execution_time": 0.02
         },
         "edges_out": {
             "name": "Edges - outgoing",
@@ -445,12 +547,16 @@
                 "total": {
                     "name": "Total number of outgoing edges",
                     "file": "RM004_1_out_edges_total_template.rq",
-                    "execute": false
+                    "execute": true,
+                    "result": "89",
+                    "execution_time": 1.03
                 },
                 "min": {
                     "name": "Minimum of outgoing edges",
                     "file": "RM004_2_out_edges_min_template.rq",
-                    "execute": false
+                    "execute": true,
+                    "result": "5",
+                    "execution_time": 0.01
                 },
                 "median": {
                     "name": "Median of outgoing edges",
@@ -458,12 +564,16 @@
                     "replace_dict": {
                         "{median_position}": "RM004_1_out_edges_total_template.rq"
                     },
-                    "execute": false
+                    "execute": true,
+                    "result": "9",
+                    "execution_time": 0.01
                 },
                 "max": {
                     "name": "Maximum of outgoing edges",
                     "file": "RM004_4_out_edges_max_template.rq",
-                    "execute": false
+                    "execute": true,
+                    "result": "27",
+                    "execution_time": 0.01
                 }
             }
         },
@@ -473,12 +583,16 @@
                 "total": {
                     "name": "Total number of incoming edges",
                     "file": "RM005_1_in_edges_total_template.rq",
-                    "execute": false
+                    "execute": true,
+                    "result": "77",
+                    "execution_time": 0.01
                 },
                 "min": {
                     "name": "Minimum of incoming edges",
                     "file": "RM005_2_in_edges_min_template.rq",
-                    "execute": false
+                    "execute": true,
+                    "result": "1",
+                    "execution_time": 0.02
                 },
                 "median": {
                     "name": "Median of incoming edges",
@@ -486,21 +600,27 @@
                     "replace_dict": {
                         "{median_position}": "RM005_1_in_edges_total_template.rq"
                     },
-                    "execute": false
+                    "execute": true,
+                    "result": "1",
+                    "execution_time": 0.02
                 },
                 "max": {
                     "name": "Maximum of incoming edges",
                     "file": "RM005_4_in_edges_max_template.rq",
-                    "execute": false
+                    "execute": true,
+                    "result": "56",
+                    "execution_time": 0.01
                 }
             }
         },
         "connectivity": {
             "name": "Connectivity",
             "file": "RM006_connectivity_template.rq",
-            "execute": false
+            "execute": true,
+            "result": "100",
+            "execution_time": 1.02
         },
-        "timestamp": "2025-03-17T10:10",
+        "timestamp": "2025-03-17T13:46",
         "file": "11_standards.md"
     },
     "software": {
@@ -521,7 +641,9 @@
         "linkage": {
             "name": "Average Linkage",
             "file": "RM003_linkage_template.rq",
-            "execute": false
+            "execute": true,
+            "result": "11.5",
+            "execution_time": 0.02
         },
         "edges_out": {
             "name": "Edges - outgoing",
@@ -529,12 +651,16 @@
                 "total": {
                     "name": "Total number of outgoing edges",
                     "file": "RM004_1_out_edges_total_template.rq",
-                    "execute": false
+                    "execute": true,
+                    "result": "147",
+                    "execution_time": 0.01
                 },
                 "min": {
                     "name": "Minimum of outgoing edges",
                     "file": "RM004_2_out_edges_min_template.rq",
-                    "execute": false
+                    "execute": true,
+                    "result": "7",
+                    "execution_time": 0.01
                 },
                 "median": {
                     "name": "Median of outgoing edges",
@@ -542,12 +668,16 @@
                     "replace_dict": {
                         "{median_position}": "RM004_1_out_edges_total_template.rq"
                     },
-                    "execute": false
+                    "execute": true,
+                    "result": "20",
+                    "execution_time": 0.01
                 },
                 "max": {
                     "name": "Maximum of outgoing edges",
                     "file": "RM004_4_out_edges_max_template.rq",
-                    "execute": false
+                    "execute": true,
+                    "result": "122",
+                    "execution_time": 0.01
                 }
             }
         },
@@ -557,12 +687,16 @@
                 "total": {
                     "name": "Total number of incoming edges",
                     "file": "RM005_1_in_edges_total_template.rq",
-                    "execute": false
+                    "execute": true,
+                    "result": "8",
+                    "execution_time": 0.02
                 },
                 "min": {
                     "name": "Minimum of incoming edges",
                     "file": "RM005_2_in_edges_min_template.rq",
-                    "execute": false
+                    "execute": true,
+                    "result": "1",
+                    "execution_time": 0.01
                 },
                 "median": {
                     "name": "Median of incoming edges",
@@ -570,21 +704,27 @@
                     "replace_dict": {
                         "{median_position}": "RM005_1_in_edges_total_template.rq"
                     },
-                    "execute": false
+                    "execute": true,
+                    "result": "1",
+                    "execution_time": 0.01
                 },
                 "max": {
                     "name": "Maximum of incoming edges",
                     "file": "RM005_4_in_edges_max_template.rq",
-                    "execute": false
+                    "execute": true,
+                    "result": "2",
+                    "execution_time": 0.02
                 }
             }
         },
         "connectivity": {
             "name": "Connectivity",
             "file": "RM006_connectivity_template.rq",
-            "execute": false
+            "execute": true,
+            "result": "1103",
+            "execution_time": 0.03
         },
-        "timestamp": "2025-03-17T10:10",
+        "timestamp": "2025-03-17T13:46",
         "file": "13_software.md"
     },
     "service": {
@@ -593,19 +733,21 @@
             "file": "RM001_instances_template.rq",
             "execute": true,
             "result": "1",
-            "execution_time": 0.01
+            "execution_time": 0.23
         },
         "assertions": {
             "name": "Number of Assertions",
             "file": "RM002_assertions_template.rq",
             "execute": true,
             "result": "1",
-            "execution_time": 0.01
+            "execution_time": 1.04
         },
         "linkage": {
             "name": "Average Linkage",
             "file": "RM003_linkage_template.rq",
-            "execute": false
+            "execute": true,
+            "result": 0,
+            "execution_time": 0.02
         },
         "edges_out": {
             "name": "Edges - outgoing",
@@ -613,12 +755,16 @@
                 "total": {
                     "name": "Total number of outgoing edges",
                     "file": "RM004_1_out_edges_total_template.rq",
-                    "execute": false
+                    "execute": true,
+                    "result": "1",
+                    "execution_time": 0.02
                 },
                 "min": {
                     "name": "Minimum of outgoing edges",
                     "file": "RM004_2_out_edges_min_template.rq",
-                    "execute": false
+                    "execute": true,
+                    "result": "14",
+                    "execution_time": 0.01
                 },
                 "median": {
                     "name": "Median of outgoing edges",
@@ -626,12 +772,16 @@
                     "replace_dict": {
                         "{median_position}": "RM004_1_out_edges_total_template.rq"
                     },
-                    "execute": false
+                    "execute": true,
+                    "result": "14",
+                    "execution_time": 0.01
                 },
                 "max": {
                     "name": "Maximum of outgoing edges",
                     "file": "RM004_4_out_edges_max_template.rq",
-                    "execute": false
+                    "execute": true,
+                    "result": "14",
+                    "execution_time": 0.01
                 }
             }
         },
@@ -641,12 +791,16 @@
                 "total": {
                     "name": "Total number of incoming edges",
                     "file": "RM005_1_in_edges_total_template.rq",
-                    "execute": false
+                    "execute": true,
+                    "result": "0",
+                    "execution_time": 0.02
                 },
                 "min": {
                     "name": "Minimum of incoming edges",
                     "file": "RM005_2_in_edges_min_template.rq",
-                    "execute": false
+                    "execute": true,
+                    "result": null,
+                    "execution_time": 0.01
                 },
                 "median": {
                     "name": "Median of incoming edges",
@@ -654,21 +808,27 @@
                     "replace_dict": {
                         "{median_position}": "RM005_1_in_edges_total_template.rq"
                     },
-                    "execute": false
+                    "execute": true,
+                    "result": null,
+                    "execution_time": 0.01
                 },
                 "max": {
                     "name": "Maximum of incoming edges",
                     "file": "RM005_4_in_edges_max_template.rq",
-                    "execute": false
+                    "execute": true,
+                    "result": null,
+                    "execution_time": 0.01
                 }
             }
         },
         "connectivity": {
             "name": "Connectivity",
             "file": "RM006_connectivity_template.rq",
-            "execute": false
+            "execute": true,
+            "result": "13",
+            "execution_time": 0.01
         },
-        "timestamp": "2025-03-17T10:10",
+        "timestamp": "2025-03-17T13:46",
         "file": "14_service.md"
     },
     "data_service": {
@@ -689,7 +849,9 @@
         "linkage": {
             "name": "Average Linkage",
             "file": "RM003_linkage_template.rq",
-            "execute": false
+            "execute": true,
+            "result": 0,
+            "execution_time": 0.05
         },
         "edges_out": {
             "name": "Edges - outgoing",
@@ -697,12 +859,16 @@
                 "total": {
                     "name": "Total number of outgoing edges",
                     "file": "RM004_1_out_edges_total_template.rq",
-                    "execute": false
+                    "execute": true,
+                    "result": "363632",
+                    "execution_time": 0.01
                 },
                 "min": {
                     "name": "Minimum of outgoing edges",
                     "file": "RM004_2_out_edges_min_template.rq",
-                    "execute": false
+                    "execute": true,
+                    "result": "11",
+                    "execution_time": 0.02
                 },
                 "median": {
                     "name": "Median of outgoing edges",
@@ -710,12 +876,16 @@
                     "replace_dict": {
                         "{median_position}": "RM004_1_out_edges_total_template.rq"
                     },
-                    "execute": false
+                    "execute": true,
+                    "result": "28",
+                    "execution_time": 0.01
                 },
                 "max": {
                     "name": "Maximum of outgoing edges",
                     "file": "RM004_4_out_edges_max_template.rq",
-                    "execute": false
+                    "execute": true,
+                    "result": "12519",
+                    "execution_time": 0.01
                 }
             }
         },
@@ -725,12 +895,16 @@
                 "total": {
                     "name": "Total number of incoming edges",
                     "file": "RM005_1_in_edges_total_template.rq",
-                    "execute": false
+                    "execute": true,
+                    "result": "0",
+                    "execution_time": 0.07
                 },
                 "min": {
                     "name": "Minimum of incoming edges",
                     "file": "RM005_2_in_edges_min_template.rq",
-                    "execute": false
+                    "execute": true,
+                    "result": null,
+                    "execution_time": 0.03
                 },
                 "median": {
                     "name": "Median of incoming edges",
@@ -738,21 +912,27 @@
                     "replace_dict": {
                         "{median_position}": "RM005_1_in_edges_total_template.rq"
                     },
-                    "execute": false
+                    "execute": true,
+                    "result": null,
+                    "execution_time": 0.03
                 },
                 "max": {
                     "name": "Maximum of incoming edges",
                     "file": "RM005_4_in_edges_max_template.rq",
-                    "execute": false
+                    "execute": true,
+                    "result": null,
+                    "execution_time": 0.03
                 }
             }
         },
         "connectivity": {
             "name": "Connectivity",
             "file": "RM006_connectivity_template.rq",
-            "execute": false
+            "execute": true,
+            "result": "58909",
+            "execution_time": 1.75
         },
-        "timestamp": "2025-03-17T10:10",
+        "timestamp": "2025-03-17T13:46",
         "file": "15_data_service.md"
     },
     "aggregator": {
@@ -773,7 +953,9 @@
         "linkage": {
             "name": "Average Linkage",
             "file": "RM003_linkage_template.rq",
-            "execute": false
+            "execute": true,
+            "result": "187",
+            "execution_time": 0.02
         },
         "edges_out": {
             "name": "Edges - outgoing",
@@ -781,12 +963,16 @@
                 "total": {
                     "name": "Total number of outgoing edges",
                     "file": "RM004_1_out_edges_total_template.rq",
-                    "execute": false
+                    "execute": true,
+                    "result": "38",
+                    "execution_time": 0.02
                 },
                 "min": {
                     "name": "Minimum of outgoing edges",
                     "file": "RM004_2_out_edges_min_template.rq",
-                    "execute": false
+                    "execute": true,
+                    "result": "8",
+                    "execution_time": 0.01
                 },
                 "median": {
                     "name": "Median of outgoing edges",
@@ -794,12 +980,16 @@
                     "replace_dict": {
                         "{median_position}": "RM004_1_out_edges_total_template.rq"
                     },
-                    "execute": false
+                    "execute": true,
+                    "result": "36",
+                    "execution_time": 0.01
                 },
                 "max": {
                     "name": "Maximum of outgoing edges",
                     "file": "RM004_4_out_edges_max_template.rq",
-                    "execute": false
+                    "execute": true,
+                    "result": "57",
+                    "execution_time": 0.01
                 }
             }
         },
@@ -809,12 +999,16 @@
                 "total": {
                     "name": "Total number of incoming edges",
                     "file": "RM005_1_in_edges_total_template.rq",
-                    "execute": false
+                    "execute": true,
+                    "result": "1",
+                    "execution_time": 0.03
                 },
                 "min": {
                     "name": "Minimum of incoming edges",
                     "file": "RM005_2_in_edges_min_template.rq",
-                    "execute": false
+                    "execute": true,
+                    "result": "151",
+                    "execution_time": 0.02
                 },
                 "median": {
                     "name": "Median of incoming edges",
@@ -822,21 +1016,27 @@
                     "replace_dict": {
                         "{median_position}": "RM005_1_in_edges_total_template.rq"
                     },
-                    "execute": false
+                    "execute": true,
+                    "result": "151",
+                    "execution_time": 0.02
                 },
                 "max": {
                     "name": "Maximum of incoming edges",
                     "file": "RM005_4_in_edges_max_template.rq",
-                    "execute": false
+                    "execute": true,
+                    "result": "151",
+                    "execution_time": 0.01
                 }
             }
         },
         "connectivity": {
             "name": "Connectivity",
             "file": "RM006_connectivity_template.rq",
-            "execute": false
+            "execute": true,
+            "result": "239",
+            "execution_time": 0.01
         },
-        "timestamp": "2025-03-17T10:10",
+        "timestamp": "2025-03-17T13:46",
         "file": "16_aggregator.md"
     },
     "person": {
@@ -857,7 +1057,9 @@
         "linkage": {
             "name": "Average Linkage",
             "file": "RM003_linkage_template.rq",
-            "execute": false
+            "execute": true,
+            "result": "4.628269848554383",
+            "execution_time": 1.05
         },
         "edges_out": {
             "name": "Edges - outgoing",
@@ -865,12 +1067,16 @@
                 "total": {
                     "name": "Total number of outgoing edges",
                     "file": "RM004_1_out_edges_total_template.rq",
-                    "execute": false
+                    "execute": true,
+                    "result": "2180",
+                    "execution_time": 0.02
                 },
                 "min": {
                     "name": "Minimum of outgoing edges",
                     "file": "RM004_2_out_edges_min_template.rq",
-                    "execute": false
+                    "execute": true,
+                    "result": "2",
+                    "execution_time": 0.01
                 },
                 "median": {
                     "name": "Median of outgoing edges",
@@ -878,12 +1084,16 @@
                     "replace_dict": {
                         "{median_position}": "RM004_1_out_edges_total_template.rq"
                     },
-                    "execute": false
+                    "execute": true,
+                    "result": "4",
+                    "execution_time": 0.01
                 },
                 "max": {
                     "name": "Maximum of outgoing edges",
                     "file": "RM004_4_out_edges_max_template.rq",
-                    "execute": false
+                    "execute": true,
+                    "result": "7",
+                    "execution_time": 0.01
                 }
             }
         },
@@ -893,12 +1103,16 @@
                 "total": {
                     "name": "Total number of incoming edges",
                     "file": "RM005_1_in_edges_total_template.rq",
-                    "execute": false
+                    "execute": true,
+                    "result": "2179",
+                    "execution_time": 0.01
                 },
                 "min": {
                     "name": "Minimum of incoming edges",
                     "file": "RM005_2_in_edges_min_template.rq",
-                    "execute": false
+                    "execute": true,
+                    "result": "1",
+                    "execution_time": 0.01
                 },
                 "median": {
                     "name": "Median of incoming edges",
@@ -906,21 +1120,27 @@
                     "replace_dict": {
                         "{median_position}": "RM005_1_in_edges_total_template.rq"
                     },
-                    "execute": false
+                    "execute": true,
+                    "result": "1",
+                    "execution_time": 0.02
                 },
                 "max": {
                     "name": "Maximum of incoming edges",
                     "file": "RM005_4_in_edges_max_template.rq",
-                    "execute": false
+                    "execute": true,
+                    "result": "2",
+                    "execution_time": 0.01
                 }
             }
         },
         "connectivity": {
             "name": "Connectivity",
             "file": "RM006_connectivity_template.rq",
-            "execute": false
+            "execute": true,
+            "result": "2",
+            "execution_time": 0.01
         },
-        "timestamp": "2025-03-17T10:10",
+        "timestamp": "2025-03-17T13:46",
         "file": "17_person.md"
     },
     "registry": {
@@ -941,7 +1161,9 @@
         "linkage": {
             "name": "Average Linkage",
             "file": "RM003_linkage_template.rq",
-            "execute": false
+            "execute": true,
+            "result": 0,
+            "execution_time": 0.02
         },
         "edges_out": {
             "name": "Edges - outgoing",
@@ -949,12 +1171,16 @@
                 "total": {
                     "name": "Total number of outgoing edges",
                     "file": "RM004_1_out_edges_total_template.rq",
-                    "execute": false
+                    "execute": true,
+                    "result": "5",
+                    "execution_time": 0.01
                 },
                 "min": {
                     "name": "Minimum of outgoing edges",
                     "file": "RM004_2_out_edges_min_template.rq",
-                    "execute": false
+                    "execute": true,
+                    "result": "7",
+                    "execution_time": 0.02
                 },
                 "median": {
                     "name": "Median of outgoing edges",
@@ -962,12 +1188,16 @@
                     "replace_dict": {
                         "{median_position}": "RM004_1_out_edges_total_template.rq"
                     },
-                    "execute": false
+                    "execute": true,
+                    "result": "9",
+                    "execution_time": 0.01
                 },
                 "max": {
                     "name": "Maximum of outgoing edges",
                     "file": "RM004_4_out_edges_max_template.rq",
-                    "execute": false
+                    "execute": true,
+                    "result": "11",
+                    "execution_time": 0.01
                 }
             }
         },
@@ -977,12 +1207,16 @@
                 "total": {
                     "name": "Total number of incoming edges",
                     "file": "RM005_1_in_edges_total_template.rq",
-                    "execute": false
+                    "execute": true,
+                    "result": "0",
+                    "execution_time": 0.01
                 },
                 "min": {
                     "name": "Minimum of incoming edges",
                     "file": "RM005_2_in_edges_min_template.rq",
-                    "execute": false
+                    "execute": true,
+                    "result": null,
+                    "execution_time": 0.01
                 },
                 "median": {
                     "name": "Median of incoming edges",
@@ -990,22 +1224,28 @@
                     "replace_dict": {
                         "{median_position}": "RM005_1_in_edges_total_template.rq"
                     },
-                    "execute": false
+                    "execute": true,
+                    "result": null,
+                    "execution_time": 0.01
                 },
                 "max": {
                     "name": "Maximum of incoming edges",
                     "file": "RM005_4_in_edges_max_template.rq",
-                    "execute": false
+                    "execute": true,
+                    "result": null,
+                    "execution_time": 0.01
                 }
             }
         },
         "connectivity": {
             "name": "Connectivity",
             "file": "RM006_connectivity_template.rq",
-            "execute": false
+            "execute": true,
+            "result": "15",
+            "execution_time": 0.01
         },
-        "timestamp": "2025-03-17T10:10",
+        "timestamp": "2025-03-17T13:46",
         "file": "18_registry.md"
     },
-    "timestamp": "2025-03-17T10:10"
+    "timestamp": "2025-03-17T13:46"
 }
\ No newline at end of file
diff --git a/scripts/kg_analysis/interfaces/__init__.py b/scripts/kg_analysis/interfaces/__init__.py
new file mode 100644
index 0000000..79f526c
--- /dev/null
+++ b/scripts/kg_analysis/interfaces/__init__.py
@@ -0,0 +1,3 @@
+from .general import query_templates as general_query_templates
+from .resources import query_templates as resource_query_templates
+from .resources import resource_types
diff --git a/scripts/kg_analysis/interfaces/general.py b/scripts/kg_analysis/interfaces/general.py
new file mode 100644
index 0000000..ded2f56
--- /dev/null
+++ b/scripts/kg_analysis/interfaces/general.py
@@ -0,0 +1,76 @@
+# -*- coding: utf-8 -*-
+# Copyright (C) 2025 TU-Dresden, ZIH
+# ralf.klammer@tu-dresden.de
+import logging
+
+log = logging.getLogger(__name__)
+
+query_templates = {
+    "instances": {
+        "name": "Instance Count",
+        "file": "GM001.rq",
+        "execute": True,
+    },
+    "assertions": {
+        "name": "Assertions Count",
+        "file": "GM002_1.rq",
+        "execute": True,
+    },
+    "linkage": {
+        "name": "Average Linkage Degree",
+        "file": "GM003_2.rq",
+        "execute": False,
+    },
+    "edges_out": {
+        "name": "Edges - outgoing",
+        "files": {
+            "total": {
+                "name": "Total number of outgoing edges",
+                "file": "GM004_2.rq",
+                "execute": True,
+            },
+            "min": {
+                "name": "Minimum of outgoing edges",
+                "file": "GM004_5.rq",
+                "execute": True,
+            },
+            "median": {
+                "name": "Median of outgoing edges",
+                "file": "GM004_4.rq",
+                "replace_dict": {"{median_position}": "GM004_2.rq"},
+                "execute": True,
+            },
+            "max": {
+                "name": "Maximum of outgoing edges",
+                "file": "GM004_6.rq",
+                "execute": True,
+            },
+        },
+    },
+    "edges_in": {
+        "name": "Edges - incoming",
+        "files": {
+            "total": {
+                "name": "Total number of incoming edges",
+                "file": "GM005_2.rq",
+                "execute": False,
+            },
+            "min": {
+                "name": "Minimum of incoming edges",
+                "file": "GM005_5.rq",
+                "execute": False,
+            },
+            "median": {
+                "name": "Median of incoming edges",
+                "file": "GM005_4.rq",
+                "replace_dict": {"{median_position}": "GM005_2.rq"},
+                "execute": False,
+            },
+            "max": {
+                "name": "Maximum of incoming edges",
+                "file": "GM005_6.rq",
+                "execute": False,
+            },
+        },
+    },
+}
diff --git a/scripts/kg_analysis/interfaces/resources.py b/scripts/kg_analysis/interfaces/resources.py
new file mode 100644
index 0000000..203685c
--- /dev/null
+++ b/scripts/kg_analysis/interfaces/resources.py
@@ -0,0 +1,140 @@
+# -*- coding: utf-8 -*-
+# Copyright (C) 2025 TU-Dresden, ZIH
+# ralf.klammer@tu-dresden.de
+import logging
+
+log = logging.getLogger(__name__)
+
+query_templates = {
+    "instances": {
+        "name": "Number of Resources",
+        "file": "RM001_instances_template.rq",
+        "execute": True,
+    },
+    "assertions": {
+        "name": "Number of Assertions",
+        "file": "RM002_assertions_template.rq",
+        "execute": True,
+    },
+    "linkage": {
+        "name": "Average Linkage",
+        "file": "RM003_linkage_template.rq",
+        "execute": True,
+    },
+    "edges_out": {
+        "name": "Edges - outgoing",
+        "files": {
+            "total": {
+                "name": "Total number of outgoing edges",
+                "file": "RM004_1_out_edges_total_template.rq",
+                "execute": True,
+            },
+            "min": {
+                "name": "Minimum of outgoing edges",
+                "file": "RM004_2_out_edges_min_template.rq",
+                "execute": True,
+            },
+            "median": {
+                "name": "Median of outgoing edges",
+                "file": "RM004_3_out_edges_median_template.rq",
+                "replace_dict": {
+                    "{median_position}": "RM004_1_out_edges_total_template.rq"
+                },
+                "execute": True,
+            },
+            "max": {
+                "name": "Maximum of outgoing edges",
+                "file": "RM004_4_out_edges_max_template.rq",
+                "execute": True,
+            },
+        },
+    },
+    "edges_in": {
+        "name": "Edges - incoming",
+        "files": {
+            "total": {
+                "name": "Total number of incoming edges",
+                "file": "RM005_1_in_edges_total_template.rq",
+                "execute": True,
+            },
+            "min": {
+                "name": "Minimum of incoming edges",
+                "file": "RM005_2_in_edges_min_template.rq",
+                "execute": True,
+            },
+            "median": {
+                "name": "Median of incoming edges",
+                "file": "RM005_3_in_edges_median_template.rq",
+                "replace_dict": {
+                    "{median_position}": "RM005_1_in_edges_total_template.rq"
+                },
+                "execute": True,
+            },
+            "max": {
+                "name": "Maximum of incoming edges",
+                "file": "RM005_4_in_edges_max_template.rq",
+                "execute": True,
+            },
+        },
+    },
+    "connectivity": {
+        "name": "Connectivity",
+        "file": "RM006_connectivity_template.rq",
+        "execute": True,
+    },
+}
+
+resource_types = {
+    "dataset": {
+        "file": "06_dataset.md",
+        "uri": "<http://www.w3.org/ns/dcat#Dataset>",
+    },
+    "publication": {
+        "file": "07_publication.md",
+        "uri": "<http://nfdi4earth.de/ontology/Registry>",
+    },
+    "learning_resource": {
+        "file": "08_learning_resource.md",
+        "uri": "<http://schema.org/LearningResource>",
+    },
+    "repository": {
+        "file": "09_repository.md",
+        "uri": "<http://nfdi4earth.de/ontology/Repository>",
+    },
+    "article_lhb": {
+        "file": "10_article_lhb.md",
+        "uri": "<http://nfdi4earth.de/ontology/LHBArticle>",
+    },
+    "standards": {
+        "file": "11_standards.md",
+        "uri": "<http://nfdi4earth.de/ontology/MetadataStandard>",
+    },
+    # "organization": {
+    #     "file": "12_organization.md",
+    #     "uri": "<http://xmlns.com/foaf/0.1/Organization>",
+    # },
+    "software": {
+        "file": "13_software.md",
+        "uri": "<http://schema.org/SoftwareSourceCode>",
+    },
+    "service": {
+        "file": "14_service.md",
+        "uri": "<http://www.w3.org/ns/sparql-service-description#Service>",
+    },
+    "data_service": {
+        "file": "15_data_service.md",
+        "uri": "<http://www.w3.org/ns/dcat#DataService>",
+    },
+    "aggregator": {
+        "file": "16_aggregator.md",
+        "uri": "<http://nfdi4earth.de/ontology/Aggregator>",
+    },
+    "person": {
+        "file": "17_person.md",
+        "uri": "<http://schema.org/Person>",
+    },
+    "registry": {
+        "file": "18_registry.md",
+        "uri": "<http://nfdi4earth.de/ontology/Registry>",
+    },
+}
diff --git a/scripts/kg_analysis/metrics_runner.py b/scripts/kg_analysis/metrics_runner.py
index 71f0ad4..48b4141 100644
--- a/scripts/kg_analysis/metrics_runner.py
+++ b/scripts/kg_analysis/metrics_runner.py
@@ -12,14 +12,23 @@ from pathlib import Path
 from time import time
 from typing import Optional
 
-from . import rprint
 from .query_runner import QueryRunner
+from .interfaces import (
+    general_query_templates,
+    resource_query_templates,
+    resource_types,
+)
 
 log = logging.getLogger(__name__)
 
 
 class MetricsRunnerBase(ABC):
-    """Base class for metric runners."""
+    """
+    Base class for metric runners that provides common functionality for:
+    - Query execution and result handling
+    - File path management
+    - JSON and TXT report generation
+    """
 
     _query_file: Optional[str] = None
     _output_file: Optional[str] = None
@@ -74,7 +83,7 @@ class MetricsRunnerBase(ABC):
             if not result["results"]["bindings"][0].values():
                 return 0
             return list(result["results"]["bindings"][0].values())[0]["value"]
-        return "-"
+        return None
 
     def save_report(self, result) -> None:
         """Run a metric query and optionally save the results."""
@@ -91,18 +100,13 @@ class MetricsRunnerBase(ABC):
                 )
             log.info(f"Results saved to {self.output_path_txt}")
 
-    def save(self, result) -> None:
-        log.warning("Deprecated: Use save_report")
-        return self.save_report(result)
-
     def save_to_json(self, dictionary, filename: Optional[str] = None):
         """
-        Saves a dictionary to a JSON file
+        Saves results dictionary to a JSON file with proper formatting
 
         Args:
-            dictionary (dict): The dictionary to save
-            filename (str): Name of the output file
-            (default: self.output_path_json)
+            dictionary (dict): Results to save
+            filename (str, optional): Target filename, uses default if None
         """
         with open(
             filename or self.output_path_json, "w", encoding="utf-8"
@@ -110,14 +114,24 @@ class MetricsRunnerBase(ABC):
             json.dump(dictionary, f, indent=4, ensure_ascii=False)
 
     def _get_result(self, query, resource_type, replace_dict=None):
+        """
+        Executes a single query and stores its result
+
+        Args:
+            query (dict): Query configuration with name, file and execute flag
+            resource_type (str): URI of the resource type to query
+            replace_dict (dict, optional): Additional replacements for query templates
+        """
         if not query["execute"]:
             return
         log.info(f"Metric: {query['name']} ({query['file']})")
-        _replace_dict = {
-            "{resource_type}": resource_type,
-        }
+
+        # Prepare replacement dictionary
+        _replace_dict = {"{resource_type}": resource_type}
         if replace_dict:
             _replace_dict.update(replace_dict)
+
+        # Execute query and store results
         result = self.query_metric(
             self.get_query_path(query["file"]),
             replace_dict=_replace_dict,
@@ -127,33 +141,34 @@ class MetricsRunnerBase(ABC):
 
     def run(self, resource_type_uri=None):
         """
-        Execute metrics analysis and save results to JSON.
+        Main execution method for metric analysis.
+        Handles both general and resource-specific metrics.
 
         Args:
             resource_type_uri (str, optional): URI of specific resource type.
-                                            If None, runs general metrics.
+                                             If None, runs general metrics.
         Returns:
-            dict: Results of the metric analysis
+            dict: Collected metrics results
         """
-        # Create deep copy of query templates
+        # Create deep copy to avoid modifying templates
         queries = deepcopy(self.query_templates)
 
-        # Process each query
+        # Process each query definition
         for query_key, query in queries.items():
             if query_key == "timestamp":
                 continue
 
             if "files" in query:
-                # Handle composite queries (edges)
+                # Handle composite metrics (like edge statistics)
                 for metric, sub_query in query["files"].items():
                     replace_dict = {}
 
-                    # Handle queries that need pre-calculated values
+                    # Pre-calculate values needed for this metric
                     if "replace_dict" in sub_query and sub_query["execute"]:
                         for dict_element, dependency_query in sub_query[
                             "replace_dict"
                         ].items():
-                            # Get total count for calculations (e.g. median)
+                            # Execute dependency query (e.g. total count for median)
                             total = self.query_metric(
                                 self.get_query_path(dependency_query),
                                 replace_dict=(
@@ -162,6 +177,8 @@ class MetricsRunnerBase(ABC):
                                     else None
                                 ),
                             )
+
+                            # Convert and validate result
                             try:
                                 total = int(total)
                             except ValueError:
@@ -170,251 +187,63 @@ class MetricsRunnerBase(ABC):
                                 )
                                 continue
 
+                            # Special handling for median calculations
                             if total and metric == "median":
-                                # Special handling for median calculations
                                 replace_dict[dict_element] = int(total) / 2
                             else:
                                 replace_dict[dict_element] = total
 
-                    # Execute the sub-query
+                    # Execute the actual metric query
                     self._get_result(
                         sub_query,
                         resource_type_uri if resource_type_uri else None,
                         replace_dict=replace_dict,
                     )
             else:
-                # Execute simple queries
+                # Handle simple metrics (single query)
                 self._get_result(
                     query, resource_type_uri if resource_type_uri else None
                 )
 
-        # Add timestamp
+        # Add execution timestamp
         queries["timestamp"] = datetime.now().isoformat(timespec="minutes")
 
         return queries
 
 
 class MetricsRunner_General(MetricsRunnerBase):
-    """Runner for General Metrics (GM001-GM005)"""
+    """Handles general metrics that apply to the entire knowledge graph"""
+
+    query_templates = general_query_templates
 
     def run(self):
+        """
+        Executes and saves general metrics
+        Returns:
+            dict: General metrics results
+        """
         output_path = "reports/metrics/general.json"
         queries = super().run()
-
         self.save_to_json(queries, output_path)
-
         return queries
 
-    query_templates = {
-        "instances": {
-            "name": "Instance Count",
-            "file": "GM001.rq",
-            "execute": True,
-        },
-        "assertions": {
-            "name": "Assertions Count",
-            "file": "GM002_1.rq",
-            "execute": False,
-        },
-        "linkage": {
-            "name": "Average Linkage Degree",
-            "file": "GM003_2.rq",
-            "execute": False,
-        },
-        "edges_out": {
-            "name": "Edges - outgoing",
-            "files": {
-                "total": {
-                    "name": "Total number of outgoing edges",
-                    "file": "GM004_2.rq",
-                    "execute": False,
-                },
-                "min": {
-                    "name": "Minimum of outgoing edges",
-                    "file": "GM004_5.rq",
-                    "execute": False,
-                },
-                "median": {
-                    "name": "Median of outgoing edges",
-                    "file": "GM004_4.rq",
-                    "replace_dict": {"{median_position}": "GM004_2.rq"},
-                    "execute": False,
-                },
-                "max": {
-                    "name": "Maximum of outgoing edges",
-                    "file": "GM004_6.rq",
-                    "execute": False,
-                },
-            },
-        },
-        "edges_in": {
-            "name": "Edges - incoming",
-            "files": {
-                "total": {
-                    "name": "Total number of incoming edges",
-                    "file": "GM005_2.rq",
-                    "execute": False,
-                },
-                "min": {
-                    "name": "Minimum of incoming edges",
-                    "file": "GM005_5.rq",
-                    "execute": False,
-                },
-                "median": {
-                    "name": "Median of incoming edges",
-                    "file": "GM005_4.rq",
-                    "replace_dict": {"{median_position}": "GM005_2.rq"},
-                    "execute": False,
-                },
-                "max": {
-                    "name": "Maximum of incoming edges",
-                    "file": "GM005_6.rq",
-                    "execute": False,
-                },
-            },
-        },
-    }
-
 
 class MetricsRunner_Resources(MetricsRunnerBase):
-    query_templates = {
-        "instances": {
-            "name": "Number of Resources",
-            "file": "RM001_instances_template.rq",
-            "execute": True,
-        },
-        "assertions": {
-            "name": "Number of Assertions",
-            "file": "RM002_assertions_template.rq",
-            "execute": True,
-        },
-        "linkage": {
-            "name": "Average Linkage",
-            "file": "RM003_linkage_template.rq",
-            "execute": False,
-        },
-        "edges_out": {
-            "name": "Edges - outgoing",
-            "files": {
-                "total": {
-                    "name": "Total number of outgoing edges",
-                    "file": "RM004_1_out_edges_total_template.rq",
-                    "execute": False,
-                },
-                "min": {
-                    "name": "Minimum of outgoing edges",
-                    "file": "RM004_2_out_edges_min_template.rq",
-                    "execute": False,
-                },
-                "median": {
-                    "name": "Median of outgoing edges",
-                    "file": "RM004_3_out_edges_median_template.rq",
-                    "replace_dict": {
-                        "{median_position}": "RM004_1_out_edges_total_template.rq"
-                    },
-                    "execute": False,
-                },
-                "max": {
-                    "name": "Maximum of outgoing edges",
-                    "file": "RM004_4_out_edges_max_template.rq",
-                    "execute": False,
-                },
-            },
-        },
-        "edges_in": {
-            "name": "Edges - incoming",
-            "files": {
-                "total": {
-                    "name": "Total number of incoming edges",
-                    "file": "RM005_1_in_edges_total_template.rq",
-                    "execute": False,
-                },
-                "min": {
-                    "name": "Minimum of incoming edges",
-                    "file": "RM005_2_in_edges_min_template.rq",
-                    "execute": False,
-                },
-                "median": {
-                    "name": "Median of incoming edges",
-                    "file": "RM005_3_in_edges_median_template.rq",
-                    "replace_dict": {
-                        "{median_position}": "RM005_1_in_edges_total_template.rq"
-                    },
-                    "execute": False,
-                },
-                "max": {
-                    "name": "Maximum of incoming edges",
-                    "file": "RM005_4_in_edges_max_template.rq",
-                    "execute": False,
-                },
-            },
-        },
-        "connectivity": {
-            "name": "Connectivity",
-            "file": "RM006_connectivity_template.rq",
-            "execute": False,
-        },
-    }
-
-    resource_types = {
-        "dataset": {
-            "file": "06_dataset.md",
-            "uri": "<http://www.w3.org/ns/dcat#Dataset>",
-        },
-        "publication": {
-            "file": "07_publication.md",
-            "uri": "<http://nfdi4earth.de/ontology/Registry>",
-        },
-        "learning_resource": {
-            "file": "08_learning_resource.md",
-            "uri": "<http://schema.org/LearningResource>",
-        },
-        "repository": {
-            "file": "09_repository.md",
-            "uri": "<http://nfdi4earth.de/ontology/Repository>",
-        },
-        "article_lhb": {
-            "file": "10_article_lhb.md",
-            "uri": "<http://nfdi4earth.de/ontology/LHBArticle>",
-        },
-        "standards": {
-            "file": "11_standards.md",
-            "uri": "<http://nfdi4earth.de/ontology/MetadataStandard>",
-        },
-        # "organization": {
-        #     "file": "12_organization.md",
-        #     "uri": "<http://xmlns.com/foaf/0.1/Organization>",
-        # },
-        "software": {
-            "file": "13_software.md",
-            "uri": "<http://schema.org/SoftwareSourceCode>",
-        },
-        "service": {
-            "file": "14_service.md",
-            "uri": "<http://www.w3.org/ns/sparql-service-description#Service>",
-        },
-        "data_service": {
-            "file": "15_data_service.md",
-            "uri": "<http://www.w3.org/ns/dcat#DataService>",
-        },
-        "aggregator": {
-            "file": "16_aggregator.md",
-            "uri": "<http://nfdi4earth.de/ontology/Aggregator>",
-        },
-        "person": {
-            "file": "17_person.md",
-            "uri": "<http://schema.org/Person>",
-        },
-        "registry": {
-            "file": "18_registry.md",
-            "uri": "<http://nfdi4earth.de/ontology/Registry>",
-        },
-    }
+    """Handles resource-specific metrics for different resource types"""
+
+    resource_types = resource_types
+    query_templates = resource_query_templates
 
     def run(self):
-        """Run metrics for all resource types"""
+        """
+        Executes metrics for each resource type and saves combined results
+        Returns:
+            dict: Resource-specific metrics results
+        """
         results = {}
+        # Execute metrics for each resource type
         for resource_type, data in self.resource_types.items():
+            log.info(f"Resource type: ###{resource_type.upper()}###")
             results[resource_type] = super().run(data["uri"])
             results[resource_type]["file"] = data["file"]
 
@@ -424,13 +253,14 @@ class MetricsRunner_Resources(MetricsRunnerBase):
 
 
 class MetricsRunner(ABC):
+    """Main entry point for executing all metrics"""
 
     def run(self):
-        """Run all metrics analysis"""
-        # Run general metrics
-        mg = MetricsRunner_General()
-        mg.run()
-
-        # Run resource-specific metrics
-        mr = MetricsRunner_Resources()
-        mr.run()
+        """
+        Executes both general and resource-specific metrics
+        """
+        # Run general metrics for entire knowledge graph
+        MetricsRunner_General().run()
+
+        # Run metrics for specific resource types
+        MetricsRunner_Resources().run()
-- 
GitLab


From 349e568a6f3b2d9c1dd64741adedc1bf06a79624 Mon Sep 17 00:00:00 2001
From: Ralf Klammer <ralf.klammer@tu-dresden.de>
Date: Mon, 17 Mar 2025 14:37:07 +0100
Subject: [PATCH 21/59] [metrics] Link to query-files in resource_metrics-macro

---
 docs/macros/main.py             |  43 +++++----
 docs/macros/resource_metrics.md |  25 ++++-
 reports/metrics/resources.json  | 156 ++++++++++++++++----------------
 3 files changed, 125 insertions(+), 99 deletions(-)

diff --git a/docs/macros/main.py b/docs/macros/main.py
index 88446fe..8f6c843 100644
--- a/docs/macros/main.py
+++ b/docs/macros/main.py
@@ -34,28 +34,30 @@ def define_env(env):
 
     def render_resource(resource_data, output, resource_type=None):
         """Helper function to render a single resource"""
-        for query_data in resource_data["queries"].values():
-            if "result" in query_data:
+        for query_data in resource_data.values():
+            if "file" in query_data:
                 if resource_type:
                     output.append(
-                        f"| {resource_type} | {query_data['name']} | {query_data['result']} |"
+                        f"| {resource_type} | {query_data['name']} | {query_data['file']} | {query_data['result']} |"
                     )
                 else:
-                    output.append(f"| {query_data['name']} | {query_data['result']} |")
-            else:
+                    output.append(
+                        f"| {query_data['name']} | [{query_data['file']}](#{query_data['file']}) | {query_data['result']} |"
+                    )
+            elif "files" in query_data:
                 if resource_type:
-                    output.append(f"| {resource_type} | **{query_data['name']}** | |")
+                    output.append(f"| {resource_type} | **{query_data['name']}** | | |")
                 else:
-                    output.append(f"| **{query_data['name']}** | |")
-                for sub_query_data in query_data["files"].values():
-                    if resource_type:
-                        output.append(
-                            f"| {resource_type} | {sub_query_data['name']} | {sub_query_data['result']} |"
-                        )
-                    else:
-                        output.append(
-                            f"| {sub_query_data['name']} | {sub_query_data['result']} |"
-                        )
+                    output.append(f"| **{query_data['name']}** | | |")
+                    for sub_query_data in query_data["files"].values():
+                        if resource_type:
+                            output.append(
+                                f"| {resource_type} | {sub_query_data['name']} | {sub_query_data['file']} | {sub_query_data['result']} |"
+                            )
+                        else:
+                            output.append(
+                                f"| {sub_query_data['name']} | [{sub_query_data['file']}](#{sub_query_data['file']}) | {sub_query_data['result']} |"
+                            )
 
     @env.macro
     def resource_metrics_table(resource_type=None):
@@ -76,13 +78,14 @@ def define_env(env):
             if resource_type not in resources:
                 return f"*No metrics found for {resource_type}*"
 
-            output.append("| Metric | Result |")
-            output.append("|--------|--------|")
+            output.append("| Metric | File | Result |")
+            output.append("|--------|------|--------|")
+            print(resources[resource_type])
             render_resource(resources[resource_type], output)
         else:
             # Render all resources
-            output.append("| Resource Type | Metric | Result |")
-            output.append("|--------------|--------|--------|")
+            output.append("| Resource Type | Metric | File | Result |")
+            output.append("|--------------|--------|------|--------|")
             for res_type, resource_data in resources.items():
                 if res_type != "timestamp":
                     render_resource(resource_data, output, res_type)
diff --git a/docs/macros/resource_metrics.md b/docs/macros/resource_metrics.md
index 4aa3b47..ce5130b 100644
--- a/docs/macros/resource_metrics.md
+++ b/docs/macros/resource_metrics.md
@@ -11,12 +11,15 @@ Resource type: {{resource_type_uri}}
 
 see also: [general metrics/instances](/metrics/general%20metrics/01_instances/)
 
+<i id="RM001_instances_template.rq">file: RM001_instances_template.rq</i>
+
 ```sparql
 {{ include_template("queries/metrics/RM001_instances_template.rq", resource_type_uri) }}
 ```
-
 ### Connectivity to other resources
 
+<i id="RM006_connectivity_template.rq">file: RM006_connectivity_template.rq</i>
+
 ```sparql
 {{ include_template("queries/metrics/RM006_connectivity_template.rq", resource_type_uri) }}
 ```
@@ -25,6 +28,8 @@ see also: [general metrics/instances](/metrics/general%20metrics/01_instances/)
 
 see also: [general metrics/assortions](/metrics/general%20metrics/02_assertions/)
 
+<i id="RM002_assertions_template.rq">file: RM002_assertions_template.rq</i>
+
 ```sparql
 {{ include_template("queries/metrics/RM002_assertions_template.rq", resource_type_uri) }}
 ```
@@ -33,6 +38,8 @@ see also: [general metrics/assortions](/metrics/general%20metrics/02_assertions/
 
 see also: [general metrics/linkage](/metrics/general%20metrics/03_linkage_degree/)
 
+<i id="RM003_linkage_template.rq">file: RM003_linkage_template.rq</i>
+
 ```sparql
 {{ include_template("queries/metrics/RM003_linkage_template.rq", resource_type_uri) }}
 ```
@@ -43,24 +50,32 @@ see also: [general metrics/outgoing edges](/metrics/general%20metrics/04_outgoin
 
 #### Total outgoing edges
 
+<i id="RM004_1_out_edges_total_template.rq">file: RM004_1_out_edges_total_template.rq</i>
+
 ```sparql
 {{ include_template("queries/metrics/RM004_1_out_edges_total_template.rq", resource_type_uri) }}
 ```
 
 #### Minimum outgoing edges
 
+<i id="RM004_2_out_edges_min_template.rq">file: RM004_2_out_edges_min_template.rq</i>
+
 ```sparql
 {{ include_template("queries/metrics/RM004_2_out_edges_min_template.rq", resource_type_uri) }}
 ```
 
 #### Median outgoing edges
 
+<i id="RM004_3_out_edges_median_template.rq">file: RM004_3_out_edges_median_template.rq</i>
+
 ```sparql
 {{ include_template("queries/metrics/RM004_3_out_edges_median_template.rq", resource_type_uri) }}
 ```
 
 #### Maximum outgoing edges
 
+<i id="RM004_4_out_edges_max_template.rq">file: RM004_4_out_edges_max_template.rq</i>
+
 ```sparql
 {{ include_template("queries/metrics/RM004_4_out_edges_max_template.rq", resource_type_uri) }}
 ```
@@ -71,24 +86,32 @@ see also: [general metrics/incoming edges](/metrics/general%20metrics/05_incomin
 
 #### Total incoming edges
 
+<i id="RM005_1_in_edges_total_template.rq">file: RM005_1_in_edges_total_template.rq</i>
+
 ```sparql
 {{ include_template("queries/metrics/RM005_1_in_edges_total_template.rq", resource_type_uri) }}
 ```
 
 #### Minimum incoming edges
 
+<i id="RM005_2_in_edges_min_template.rq">file: RM005_2_in_edges_min_template.rq</i>
+
 ```sparql
 {{ include_template("queries/metrics/RM005_2_in_edges_min_template.rq", resource_type_uri) }}
 ```
 
 #### Median incoming edges
 
+<i id="RM005_3_in_edges_median_template.rq">file: RM005_3_in_edges_median_template.rq</i>
+
 ```sparql
 {{ include_template("queries/metrics/RM005_3_in_edges_median_template.rq", resource_type_uri) }}
 ```
 
 #### Maximum incoming edges
 
+<i id="RM005_4_in_edges_max_template.rq">file: RM005_4_in_edges_max_template.rq</i>
+
 ```sparql
 {{ include_template("queries/metrics/RM005_4_in_edges_max_template.rq", resource_type_uri) }}
 ```
diff --git a/reports/metrics/resources.json b/reports/metrics/resources.json
index f3a8d17..0953a73 100644
--- a/reports/metrics/resources.json
+++ b/reports/metrics/resources.json
@@ -5,21 +5,21 @@
             "file": "RM001_instances_template.rq",
             "execute": true,
             "result": "724903",
-            "execution_time": 0.01
+            "execution_time": 1.31
         },
         "assertions": {
             "name": "Number of Assertions",
             "file": "RM002_assertions_template.rq",
             "execute": true,
             "result": "724904",
-            "execution_time": 0.01
+            "execution_time": 0.02
         },
         "linkage": {
             "name": "Average Linkage",
             "file": "RM003_linkage_template.rq",
             "execute": true,
             "result": "68",
-            "execution_time": 0.07
+            "execution_time": 0.01
         },
         "edges_out": {
             "name": "Edges - outgoing",
@@ -36,7 +36,7 @@
                     "file": "RM004_2_out_edges_min_template.rq",
                     "execute": true,
                     "result": "6",
-                    "execution_time": 0.01
+                    "execution_time": 1.04
                 },
                 "median": {
                     "name": "Median of outgoing edges",
@@ -65,14 +65,14 @@
                     "file": "RM005_1_in_edges_total_template.rq",
                     "execute": true,
                     "result": "4",
-                    "execution_time": 0.12
+                    "execution_time": 0.01
                 },
                 "min": {
                     "name": "Minimum of incoming edges",
                     "file": "RM005_2_in_edges_min_template.rq",
                     "execute": true,
                     "result": "1",
-                    "execution_time": 0.04
+                    "execution_time": 0.01
                 },
                 "median": {
                     "name": "Median of incoming edges",
@@ -82,14 +82,14 @@
                     },
                     "execute": true,
                     "result": "1",
-                    "execution_time": 1.11
+                    "execution_time": 0.01
                 },
                 "max": {
                     "name": "Maximum of incoming edges",
                     "file": "RM005_4_in_edges_max_template.rq",
                     "execute": true,
                     "result": "107",
-                    "execution_time": 0.05
+                    "execution_time": 0.01
                 }
             }
         },
@@ -98,9 +98,9 @@
             "file": "RM006_connectivity_template.rq",
             "execute": true,
             "result": "427594",
-            "execution_time": 0.01
+            "execution_time": 4.33
         },
-        "timestamp": "2025-03-17T13:46",
+        "timestamp": "2025-03-17T13:55",
         "file": "06_dataset.md"
     },
     "publication": {
@@ -109,21 +109,21 @@
             "file": "RM001_instances_template.rq",
             "execute": true,
             "result": "5",
-            "execution_time": 0.01
+            "execution_time": 0.02
         },
         "assertions": {
             "name": "Number of Assertions",
             "file": "RM002_assertions_template.rq",
             "execute": true,
             "result": "6",
-            "execution_time": 1.05
+            "execution_time": 0.01
         },
         "linkage": {
             "name": "Average Linkage",
             "file": "RM003_linkage_template.rq",
             "execute": true,
             "result": 0,
-            "execution_time": 0.02
+            "execution_time": 0.01
         },
         "edges_out": {
             "name": "Edges - outgoing",
@@ -150,14 +150,14 @@
                     },
                     "execute": true,
                     "result": "9",
-                    "execution_time": 1.03
+                    "execution_time": 0.01
                 },
                 "max": {
                     "name": "Maximum of outgoing edges",
                     "file": "RM004_4_out_edges_max_template.rq",
                     "execute": true,
                     "result": "11",
-                    "execution_time": 0.02
+                    "execution_time": 0.01
                 }
             }
         },
@@ -169,7 +169,7 @@
                     "file": "RM005_1_in_edges_total_template.rq",
                     "execute": true,
                     "result": "0",
-                    "execution_time": 0.02
+                    "execution_time": 0.01
                 },
                 "min": {
                     "name": "Minimum of incoming edges",
@@ -204,7 +204,7 @@
             "result": "15",
             "execution_time": 0.01
         },
-        "timestamp": "2025-03-17T13:46",
+        "timestamp": "2025-03-17T13:55",
         "file": "07_publication.md"
     },
     "learning_resource": {
@@ -213,7 +213,7 @@
             "file": "RM001_instances_template.rq",
             "execute": true,
             "result": "512",
-            "execution_time": 0.01
+            "execution_time": 0.02
         },
         "assertions": {
             "name": "Number of Assertions",
@@ -227,7 +227,7 @@
             "file": "RM003_linkage_template.rq",
             "execute": true,
             "result": "25",
-            "execution_time": 0.02
+            "execution_time": 0.01
         },
         "edges_out": {
             "name": "Edges - outgoing",
@@ -237,14 +237,14 @@
                     "file": "RM004_1_out_edges_total_template.rq",
                     "execute": true,
                     "result": "512",
-                    "execution_time": 1.02
+                    "execution_time": 0.01
                 },
                 "min": {
                     "name": "Minimum of outgoing edges",
                     "file": "RM004_2_out_edges_min_template.rq",
                     "execute": true,
                     "result": "10",
-                    "execution_time": 0.02
+                    "execution_time": 0.01
                 },
                 "median": {
                     "name": "Median of outgoing edges",
@@ -254,7 +254,7 @@
                     },
                     "execute": true,
                     "result": "15",
-                    "execution_time": 0.02
+                    "execution_time": 0.01
                 },
                 "max": {
                     "name": "Maximum of outgoing edges",
@@ -273,7 +273,7 @@
                     "file": "RM005_1_in_edges_total_template.rq",
                     "execute": true,
                     "result": "1",
-                    "execution_time": 0.02
+                    "execution_time": 0.01
                 },
                 "min": {
                     "name": "Minimum of incoming edges",
@@ -308,7 +308,7 @@
             "result": "530",
             "execution_time": 0.01
         },
-        "timestamp": "2025-03-17T13:46",
+        "timestamp": "2025-03-17T13:55",
         "file": "08_learning_resource.md"
     },
     "repository": {
@@ -331,7 +331,7 @@
             "file": "RM003_linkage_template.rq",
             "execute": true,
             "result": "90212",
-            "execution_time": 0.02
+            "execution_time": 1.04
         },
         "edges_out": {
             "name": "Edges - outgoing",
@@ -377,14 +377,14 @@
                     "file": "RM005_1_in_edges_total_template.rq",
                     "execute": true,
                     "result": "5",
-                    "execution_time": 0.06
+                    "execution_time": 0.01
                 },
                 "min": {
                     "name": "Minimum of incoming edges",
                     "file": "RM005_2_in_edges_min_template.rq",
                     "execute": true,
                     "result": "1417",
-                    "execution_time": 0.03
+                    "execution_time": 0.01
                 },
                 "median": {
                     "name": "Median of incoming edges",
@@ -401,7 +401,7 @@
                     "file": "RM005_4_in_edges_max_template.rq",
                     "execute": true,
                     "result": "432941",
-                    "execution_time": 1.09
+                    "execution_time": 0.01
                 }
             }
         },
@@ -412,7 +412,7 @@
             "result": "825",
             "execution_time": 0.01
         },
-        "timestamp": "2025-03-17T13:46",
+        "timestamp": "2025-03-17T13:55",
         "file": "09_repository.md"
     },
     "article_lhb": {
@@ -435,7 +435,7 @@
             "file": "RM003_linkage_template.rq",
             "execute": true,
             "result": "33.245098039215686",
-            "execution_time": 0.02
+            "execution_time": 0.01
         },
         "edges_out": {
             "name": "Edges - outgoing",
@@ -469,7 +469,7 @@
                     "file": "RM004_4_out_edges_max_template.rq",
                     "execute": true,
                     "result": "62",
-                    "execution_time": 0.01
+                    "execution_time": 1.05
                 }
             }
         },
@@ -481,14 +481,14 @@
                     "file": "RM005_1_in_edges_total_template.rq",
                     "execute": true,
                     "result": "102",
-                    "execution_time": 0.02
+                    "execution_time": 0.01
                 },
                 "min": {
                     "name": "Minimum of incoming edges",
                     "file": "RM005_2_in_edges_min_template.rq",
                     "execute": true,
                     "result": "1",
-                    "execution_time": 0.02
+                    "execution_time": 0.01
                 },
                 "median": {
                     "name": "Median of incoming edges",
@@ -498,14 +498,14 @@
                     },
                     "execute": true,
                     "result": "2",
-                    "execution_time": 0.01
+                    "execution_time": 0.02
                 },
                 "max": {
                     "name": "Maximum of incoming edges",
                     "file": "RM005_4_in_edges_max_template.rq",
                     "execute": true,
                     "result": "24",
-                    "execution_time": 1.08
+                    "execution_time": 0.01
                 }
             }
         },
@@ -516,7 +516,7 @@
             "result": "592",
             "execution_time": 0.01
         },
-        "timestamp": "2025-03-17T13:46",
+        "timestamp": "2025-03-17T13:55",
         "file": "10_article_lhb.md"
     },
     "standards": {
@@ -539,7 +539,7 @@
             "file": "RM003_linkage_template.rq",
             "execute": true,
             "result": "13.714285714285714",
-            "execution_time": 0.02
+            "execution_time": 0.01
         },
         "edges_out": {
             "name": "Edges - outgoing",
@@ -549,7 +549,7 @@
                     "file": "RM004_1_out_edges_total_template.rq",
                     "execute": true,
                     "result": "89",
-                    "execution_time": 1.03
+                    "execution_time": 0.01
                 },
                 "min": {
                     "name": "Minimum of outgoing edges",
@@ -592,7 +592,7 @@
                     "file": "RM005_2_in_edges_min_template.rq",
                     "execute": true,
                     "result": "1",
-                    "execution_time": 0.02
+                    "execution_time": 0.01
                 },
                 "median": {
                     "name": "Median of incoming edges",
@@ -602,7 +602,7 @@
                     },
                     "execute": true,
                     "result": "1",
-                    "execution_time": 0.02
+                    "execution_time": 0.01
                 },
                 "max": {
                     "name": "Maximum of incoming edges",
@@ -618,9 +618,9 @@
             "file": "RM006_connectivity_template.rq",
             "execute": true,
             "result": "100",
-            "execution_time": 1.02
+            "execution_time": 0.01
         },
-        "timestamp": "2025-03-17T13:46",
+        "timestamp": "2025-03-17T13:55",
         "file": "11_standards.md"
     },
     "software": {
@@ -643,7 +643,7 @@
             "file": "RM003_linkage_template.rq",
             "execute": true,
             "result": "11.5",
-            "execution_time": 0.02
+            "execution_time": 0.01
         },
         "edges_out": {
             "name": "Edges - outgoing",
@@ -689,7 +689,7 @@
                     "file": "RM005_1_in_edges_total_template.rq",
                     "execute": true,
                     "result": "8",
-                    "execution_time": 0.02
+                    "execution_time": 0.01
                 },
                 "min": {
                     "name": "Minimum of incoming edges",
@@ -713,7 +713,7 @@
                     "file": "RM005_4_in_edges_max_template.rq",
                     "execute": true,
                     "result": "2",
-                    "execution_time": 0.02
+                    "execution_time": 0.01
                 }
             }
         },
@@ -722,9 +722,9 @@
             "file": "RM006_connectivity_template.rq",
             "execute": true,
             "result": "1103",
-            "execution_time": 0.03
+            "execution_time": 0.01
         },
-        "timestamp": "2025-03-17T13:46",
+        "timestamp": "2025-03-17T13:55",
         "file": "13_software.md"
     },
     "service": {
@@ -733,21 +733,21 @@
             "file": "RM001_instances_template.rq",
             "execute": true,
             "result": "1",
-            "execution_time": 0.23
+            "execution_time": 0.01
         },
         "assertions": {
             "name": "Number of Assertions",
             "file": "RM002_assertions_template.rq",
             "execute": true,
             "result": "1",
-            "execution_time": 1.04
+            "execution_time": 0.01
         },
         "linkage": {
             "name": "Average Linkage",
             "file": "RM003_linkage_template.rq",
             "execute": true,
             "result": 0,
-            "execution_time": 0.02
+            "execution_time": 0.01
         },
         "edges_out": {
             "name": "Edges - outgoing",
@@ -757,14 +757,14 @@
                     "file": "RM004_1_out_edges_total_template.rq",
                     "execute": true,
                     "result": "1",
-                    "execution_time": 0.02
+                    "execution_time": 0.01
                 },
                 "min": {
                     "name": "Minimum of outgoing edges",
                     "file": "RM004_2_out_edges_min_template.rq",
                     "execute": true,
                     "result": "14",
-                    "execution_time": 0.01
+                    "execution_time": 0.02
                 },
                 "median": {
                     "name": "Median of outgoing edges",
@@ -793,7 +793,7 @@
                     "file": "RM005_1_in_edges_total_template.rq",
                     "execute": true,
                     "result": "0",
-                    "execution_time": 0.02
+                    "execution_time": 0.01
                 },
                 "min": {
                     "name": "Minimum of incoming edges",
@@ -828,7 +828,7 @@
             "result": "13",
             "execution_time": 0.01
         },
-        "timestamp": "2025-03-17T13:46",
+        "timestamp": "2025-03-17T13:55",
         "file": "14_service.md"
     },
     "data_service": {
@@ -837,7 +837,7 @@
             "file": "RM001_instances_template.rq",
             "execute": true,
             "result": "363632",
-            "execution_time": 0.01
+            "execution_time": 0.13
         },
         "assertions": {
             "name": "Number of Assertions",
@@ -851,7 +851,7 @@
             "file": "RM003_linkage_template.rq",
             "execute": true,
             "result": 0,
-            "execution_time": 0.05
+            "execution_time": 0.02
         },
         "edges_out": {
             "name": "Edges - outgoing",
@@ -897,14 +897,14 @@
                     "file": "RM005_1_in_edges_total_template.rq",
                     "execute": true,
                     "result": "0",
-                    "execution_time": 0.07
+                    "execution_time": 0.01
                 },
                 "min": {
                     "name": "Minimum of incoming edges",
                     "file": "RM005_2_in_edges_min_template.rq",
                     "execute": true,
                     "result": null,
-                    "execution_time": 0.03
+                    "execution_time": 0.01
                 },
                 "median": {
                     "name": "Median of incoming edges",
@@ -914,14 +914,14 @@
                     },
                     "execute": true,
                     "result": null,
-                    "execution_time": 0.03
+                    "execution_time": 0.01
                 },
                 "max": {
                     "name": "Maximum of incoming edges",
                     "file": "RM005_4_in_edges_max_template.rq",
                     "execute": true,
                     "result": null,
-                    "execution_time": 0.03
+                    "execution_time": 0.01
                 }
             }
         },
@@ -930,9 +930,9 @@
             "file": "RM006_connectivity_template.rq",
             "execute": true,
             "result": "58909",
-            "execution_time": 1.75
+            "execution_time": 0.01
         },
-        "timestamp": "2025-03-17T13:46",
+        "timestamp": "2025-03-17T13:55",
         "file": "15_data_service.md"
     },
     "aggregator": {
@@ -955,7 +955,7 @@
             "file": "RM003_linkage_template.rq",
             "execute": true,
             "result": "187",
-            "execution_time": 0.02
+            "execution_time": 0.01
         },
         "edges_out": {
             "name": "Edges - outgoing",
@@ -965,7 +965,7 @@
                     "file": "RM004_1_out_edges_total_template.rq",
                     "execute": true,
                     "result": "38",
-                    "execution_time": 0.02
+                    "execution_time": 0.01
                 },
                 "min": {
                     "name": "Minimum of outgoing edges",
@@ -1001,14 +1001,14 @@
                     "file": "RM005_1_in_edges_total_template.rq",
                     "execute": true,
                     "result": "1",
-                    "execution_time": 0.03
+                    "execution_time": 0.01
                 },
                 "min": {
                     "name": "Minimum of incoming edges",
                     "file": "RM005_2_in_edges_min_template.rq",
                     "execute": true,
                     "result": "151",
-                    "execution_time": 0.02
+                    "execution_time": 0.01
                 },
                 "median": {
                     "name": "Median of incoming edges",
@@ -1018,7 +1018,7 @@
                     },
                     "execute": true,
                     "result": "151",
-                    "execution_time": 0.02
+                    "execution_time": 0.01
                 },
                 "max": {
                     "name": "Maximum of incoming edges",
@@ -1036,7 +1036,7 @@
             "result": "239",
             "execution_time": 0.01
         },
-        "timestamp": "2025-03-17T13:46",
+        "timestamp": "2025-03-17T13:55",
         "file": "16_aggregator.md"
     },
     "person": {
@@ -1045,7 +1045,7 @@
             "file": "RM001_instances_template.rq",
             "execute": true,
             "result": "2180",
-            "execution_time": 0.01
+            "execution_time": 0.02
         },
         "assertions": {
             "name": "Number of Assertions",
@@ -1059,7 +1059,7 @@
             "file": "RM003_linkage_template.rq",
             "execute": true,
             "result": "4.628269848554383",
-            "execution_time": 1.05
+            "execution_time": 0.01
         },
         "edges_out": {
             "name": "Edges - outgoing",
@@ -1069,7 +1069,7 @@
                     "file": "RM004_1_out_edges_total_template.rq",
                     "execute": true,
                     "result": "2180",
-                    "execution_time": 0.02
+                    "execution_time": 0.01
                 },
                 "min": {
                     "name": "Minimum of outgoing edges",
@@ -1122,7 +1122,7 @@
                     },
                     "execute": true,
                     "result": "1",
-                    "execution_time": 0.02
+                    "execution_time": 0.01
                 },
                 "max": {
                     "name": "Maximum of incoming edges",
@@ -1140,7 +1140,7 @@
             "result": "2",
             "execution_time": 0.01
         },
-        "timestamp": "2025-03-17T13:46",
+        "timestamp": "2025-03-17T13:55",
         "file": "17_person.md"
     },
     "registry": {
@@ -1149,7 +1149,7 @@
             "file": "RM001_instances_template.rq",
             "execute": true,
             "result": "5",
-            "execution_time": 0.01
+            "execution_time": 1.04
         },
         "assertions": {
             "name": "Number of Assertions",
@@ -1163,7 +1163,7 @@
             "file": "RM003_linkage_template.rq",
             "execute": true,
             "result": 0,
-            "execution_time": 0.02
+            "execution_time": 0.01
         },
         "edges_out": {
             "name": "Edges - outgoing",
@@ -1180,7 +1180,7 @@
                     "file": "RM004_2_out_edges_min_template.rq",
                     "execute": true,
                     "result": "7",
-                    "execution_time": 0.02
+                    "execution_time": 0.01
                 },
                 "median": {
                     "name": "Median of outgoing edges",
@@ -1244,8 +1244,8 @@
             "result": "15",
             "execution_time": 0.01
         },
-        "timestamp": "2025-03-17T13:46",
+        "timestamp": "2025-03-17T13:55",
         "file": "18_registry.md"
     },
-    "timestamp": "2025-03-17T13:46"
+    "timestamp": "2025-03-17T13:55"
 }
\ No newline at end of file
-- 
GitLab


From b1afd20c3dae83971b8def5485bcbf1710fec388 Mon Sep 17 00:00:00 2001
From: Ralf Klammer <ralf.klammer@tu-dresden.de>
Date: Mon, 17 Mar 2025 16:07:39 +0100
Subject: [PATCH 22/59] [metrics] 1 Table per general metric + total tables in
 overview

---
 docs/macros/main.py                           | 124 +++++++++++++++++-
 docs/metrics/general metrics/01_instances.md  |  10 +-
 docs/metrics/general metrics/02_assertions.md |   4 +-
 .../general metrics/03_linkage_degree.md      |   4 +-
 .../general metrics/04_outgoing_edges.md      |   4 +-
 .../general metrics/05_incoming_edges.md      |  10 +-
 docs/metrics/index.md                         |  39 +++++-
 reports/metrics/general.json                  |  96 ++++++++++++++
 reports/metrics/resources.json                |  74 +++++------
 scripts/kg_analysis/interfaces/general.py     |  10 +-
 10 files changed, 308 insertions(+), 67 deletions(-)
 create mode 100644 reports/metrics/general.json

diff --git a/docs/macros/main.py b/docs/macros/main.py
index 8f6c843..ad7ea4c 100644
--- a/docs/macros/main.py
+++ b/docs/macros/main.py
@@ -78,16 +78,134 @@ def define_env(env):
             if resource_type not in resources:
                 return f"*No metrics found for {resource_type}*"
 
-            output.append("| Metric | File | Result |")
+            output.append("| Metric | Query (file) | Result |")
             output.append("|--------|------|--------|")
-            print(resources[resource_type])
             render_resource(resources[resource_type], output)
         else:
             # Render all resources
-            output.append("| Resource Type | Metric | File | Result |")
+            output.append("| Resource Type | Metric | Query (file) | Result |")
             output.append("|--------------|--------|------|--------|")
             for res_type, resource_data in resources.items():
                 if res_type != "timestamp":
                     render_resource(resource_data, output, res_type)
 
         return "\n".join(output)
+
+    @env.macro
+    def resources_metrics_table():
+        """Rendert eine einfache Übersichtstabelle der Resource-Metriken"""
+        try:
+            metrics_file = Path("reports/metrics/resources.json")
+            with open(metrics_file, "r", encoding="utf-8") as f:
+                resources = json.load(f)
+        except (FileNotFoundError, json.JSONDecodeError) as e:
+            return f"*Fehler beim Laden der Metriken: {e}*"
+
+        output = []
+
+        resource_names = [
+            key for key, resource in resources.items() if isinstance(resource, dict)
+        ]
+        ordered_resource_names = sorted(resource_names)
+
+        metric_names = []
+        rows = []
+        for resource_name in ordered_resource_names:
+            resource = resources[resource_name]
+            row = f"| {resource_name.upper()} |"
+            for metric in resource.values():
+                if isinstance(metric, str):
+                    continue
+                elif "files" in metric:
+                    for sub_metric in metric["files"].values():
+                        if sub_metric["name"] not in metric_names:
+                            metric_names.append(sub_metric["name"])
+                        row += f" {sub_metric.get('result', '-')} |"
+                elif metric["name"] not in metric_names:
+                    row += f" {metric.get('result', '-')} |"
+                    metric_names.append(metric["name"])
+            rows.append(row)
+
+        output.append(f"| Resource type | {' | '.join(metric_names)} |")
+        output.append(f"| --- |{' | '.join(['---' for m in metric_names])} |")
+        for row in rows:
+            output.append(row)
+
+        output.append(f"\n*Last updated: {resources.get("timestamp", "-")}*")
+        return "\n".join(output)
+
+    @env.macro
+    def render_metric_table(metric_key):
+        """
+        Renders a specific metric as markdown table.
+
+        Args:
+            metric_key (str): Key of the metric to render (e.g. 'instances', 'edges_out')
+        """
+        try:
+            metrics_file = Path("reports/metrics/general.json")
+            with open(metrics_file, "r", encoding="utf-8") as f:
+                metrics = json.load(f)
+        except (FileNotFoundError, json.JSONDecodeError) as e:
+            return f"*Error loading metrics: {e}*"
+
+        if metric_key not in metrics:
+            return f"*No data found for metric: {metric_key}*"
+
+        output = []
+        timestamp = metrics.get("timestamp", "unknown")
+        output.append(f"*Last updated: {timestamp}*\n")
+
+        output.append("| Metric | Query (file) | Result |")
+        output.append("|--------|------|--------|")
+
+        metric_data = metrics[metric_key]
+        if "files" in metric_data:
+            # Handle composite metrics (like edge statistics)
+            output.append(f"| **{metric_data['name']}** | | |")
+            for sub_query in metric_data["files"].values():
+                output.append(
+                    f"| {sub_query['name']} | [{sub_query['file']}](#{sub_query['file']}) | {sub_query.get('result', '-')} |"
+                )
+        else:
+            # Handle simple metrics
+            output.append(
+                f"| {metric_data['name']} | [{metric_data['file']}](#{metric_data['file']}) | {metric_data.get('result', '-')} |"
+            )
+
+        return "\n".join(output)
+
+    @env.macro
+    def general_metrics_table():
+        """Renders a complete overview table of all general metrics"""
+        try:
+            metrics_file = Path("reports/metrics/general.json")
+            with open(metrics_file, "r", encoding="utf-8") as f:
+                metrics = json.load(f)
+        except (FileNotFoundError, json.JSONDecodeError) as e:
+            return f"*Error loading metrics: {e}*"
+
+        output = []
+
+        output.append("| Metric | File | Result |")
+        output.append("|--------|------|--------|")
+
+        for metric_key, metric_data in metrics.items():
+            if metric_key == "timestamp":
+                continue
+
+            if "files" in metric_data:
+                # Handle composite metrics
+                output.append(f"| **{metric_data['name']}** | | |")
+                for sub_query in metric_data["files"].values():
+                    output.append(
+                        f"| {sub_query['name']} | {sub_query['file']} | {sub_query.get('result')} |"
+                    )
+            else:
+                # Handle simple metrics
+                output.append(
+                    f"| {metric_data['name']} | {metric_data['file']} | {metric_data.get('result')} |"
+                )
+
+        output.append(f"\n*Last updated: {metrics.get("timestamp", "-")}*")
+        return "\n".join(output)
diff --git a/docs/metrics/general metrics/01_instances.md b/docs/metrics/general metrics/01_instances.md
index a869138..150c6be 100644
--- a/docs/metrics/general metrics/01_instances.md	
+++ b/docs/metrics/general metrics/01_instances.md	
@@ -1,4 +1,4 @@
-# 01 - Instances in a graph
+# Instances in a graph
 
 This metric counts the total number of instances (nodes) in an RDF graph. An instance is counted as any node that appears as either a subject or object in a triple. The instance count provides a fundamental measure of the graph's size and gives a first indication of its complexity.
 
@@ -9,6 +9,8 @@ The metric helps to:
 - Compare different graph versions or datasets
 - Establish a baseline for other metrics
 
+{{ render_metric_table('instances') }}
+
 ## Queries
 
 ### Count Number of instances in a graph
@@ -16,9 +18,3 @@ The metric helps to:
 ```sparql
 {{ include_if_exists("queries/metrics/GM001.rq") }}
 ```
-
-## Results
-{{ include_if_exists("reports/metrics/GM001.txt", start_line=1) }}
-
-Last execution:
-{{ include_if_exists("reports/metrics/GM001.txt", single_line=0) }}
\ No newline at end of file
diff --git a/docs/metrics/general metrics/02_assertions.md b/docs/metrics/general metrics/02_assertions.md
index c2d3927..e671c34 100644
--- a/docs/metrics/general metrics/02_assertions.md	
+++ b/docs/metrics/general metrics/02_assertions.md	
@@ -1,4 +1,4 @@
-# 02 - Assertions in a graph
+# Assertions in a graph
 
 This metric counts the total number of assertions (triples/edges) in an RDF graph. An assertion is any statement in the form of subject-predicate-object that exists in the graph. The assertion count provides a fundamental measure of the graph's connectivity and density.
 
@@ -9,6 +9,8 @@ The metric helps to:
 - Track the growth of relationships over time
 - Compare connectivity between different graph versions
 
+{{ render_metric_table('assertions') }}
+
 ## Queries
 
 ### Count Total Number of Assertions
diff --git a/docs/metrics/general metrics/03_linkage_degree.md b/docs/metrics/general metrics/03_linkage_degree.md
index ebfd0ee..86bdbc9 100644
--- a/docs/metrics/general metrics/03_linkage_degree.md	
+++ b/docs/metrics/general metrics/03_linkage_degree.md	
@@ -1,4 +1,4 @@
-# 03 - Linkage Degree Analysis
+# Linkage Degree Analysis
 
 This metric analyzes the connectivity patterns in the graph by measuring the linkage degree of entities. The linkage degree represents how well entities are connected to other entities through relationships. It helps understand the graph's structural characteristics and identifies patterns of connectivity.
 
@@ -9,6 +9,8 @@ The metric provides insights into:
 - Identification of highly connected or isolated entities
 - Overall graph connectivity patterns
 
+{{ render_metric_table('linkage') }}
+
 ## Queries
 
 ### Average Linkage Degree
diff --git a/docs/metrics/general metrics/04_outgoing_edges.md b/docs/metrics/general metrics/04_outgoing_edges.md
index 5a4afb4..f27874e 100644
--- a/docs/metrics/general metrics/04_outgoing_edges.md	
+++ b/docs/metrics/general metrics/04_outgoing_edges.md	
@@ -1,7 +1,9 @@
-# 04 - Outgoing Edges
+# Outgoing Edges
 
 This metric determines the median number of outgoing edges across all nodes in the graph. The calculation requires multiple steps.
 
+{{ render_metric_table('edges_out') }}
+
 ## Queries
 
 ### Step 1: Count Outgoing Edges Per Node
diff --git a/docs/metrics/general metrics/05_incoming_edges.md b/docs/metrics/general metrics/05_incoming_edges.md
index 79fc98b..1f9c99e 100644
--- a/docs/metrics/general metrics/05_incoming_edges.md	
+++ b/docs/metrics/general metrics/05_incoming_edges.md	
@@ -1,7 +1,9 @@
-# 05 - Incoming Edges
+# Incoming Edges
 
 This metric determines the median number of incoming edges across all nodes in the graph. The calculation requires multiple steps.
 
+{{ render_metric_table('edges_in') }}
+
 ## Queries
 
 ### Step 1: Count Incoming Edges Per Node
@@ -41,9 +43,3 @@ Using the total node count (n), median position is: position = (n+1)/2
 ```sparql
 {{ include_if_exists("queries/metrics/GM005_6.rq") }}
 ```
-
-## Results
-{{ include_if_exists("reports/metrics/GM005.txt", start_line=1) }}
-
-Last execution:
-{{ include_if_exists("reports/metrics/GM005.txt", single_line=0) }}
diff --git a/docs/metrics/index.md b/docs/metrics/index.md
index b97d544..f3e7249 100644
--- a/docs/metrics/index.md
+++ b/docs/metrics/index.md
@@ -1,10 +1,39 @@
 # Overview
 
-The KnowledgeGraph metrics provide quantitative insights into the structure and content of our knowledge graph. These measurements help us to:
+The NFDI4Earth Knowledge Graph metrics provide quantitative insights into our semantic data structure. We distinguish between two main metric categories:
 
-- Understand the graph's size and complexity
+## General Metrics
+
+These metrics analyze the entire knowledge graph structure and provide insights into:
+
+- Overall size and complexity
+- Connection patterns
+- Graph density and distribution
+- Edge statistics (incoming/outgoing)
+
+{{ general_metrics_table() }}
+
+## Resource-specific Metrics
+
+These metrics focus on specific resource types within the knowledge graph:
+
+{{ resources_metrics_table() }}
+
+## About the Metrics
+
+All metrics:
+
+- Are implemented as SPARQL queries (stored as `.rq` files)
+- Can be executed against the [NFDI4Earth KnowledgeGraph endpoint](https://sparql.knowledgehub.nfdi4earth.de)
+- Include execution timestamps
+
+## Purpose
+
+These measurements help us to:
+
+- Monitor the knowledge graph's growth and development
 - Evaluate the coverage of earth science domains
 - Identify areas for potential improvement
-- Monitor the graph's growth and development
-
-The queries are stored in separate `.rq` files and can be executed against the NFDI4Earth [KnowledgeGraph endpoint](https://sparql.knowledgehub.nfdi4earth.de).
+- Understand interconnections between different resource types
+- Guide data quality improvements
+- Track the integration of new resources
diff --git a/reports/metrics/general.json b/reports/metrics/general.json
new file mode 100644
index 0000000..511a23d
--- /dev/null
+++ b/reports/metrics/general.json
@@ -0,0 +1,96 @@
+{
+    "instances": {
+        "name": "Instance Count",
+        "file": "GM001.rq",
+        "execute": true,
+        "result": "1252089",
+        "execution_time": 1.06
+    },
+    "assertions": {
+        "name": "Assertions Count",
+        "file": "GM002_1.rq",
+        "execute": true,
+        "result": "40786353",
+        "execution_time": 0.01
+    },
+    "linkage": {
+        "name": "Average Linkage Degree",
+        "file": "GM003_2.rq",
+        "execute": true,
+        "result": "3.24508957824795",
+        "execution_time": 0.01
+    },
+    "edges_out": {
+        "name": "Edges - outgoing",
+        "files": {
+            "total": {
+                "name": "Total number of outgoing edges",
+                "file": "GM004_2.rq",
+                "execute": true,
+                "result": "6705836",
+                "execution_time": 0.01
+            },
+            "min": {
+                "name": "Minimum of outgoing edges",
+                "file": "GM004_5.rq",
+                "execute": true,
+                "result": "1",
+                "execution_time": 0.01
+            },
+            "median": {
+                "name": "Median of outgoing edges",
+                "file": "GM004_4.rq",
+                "replace_dict": {
+                    "{median_position}": "GM004_2.rq"
+                },
+                "execute": true,
+                "result": "2",
+                "execution_time": 0.01
+            },
+            "max": {
+                "name": "Maximum of outgoing edges",
+                "file": "GM004_6.rq",
+                "execute": true,
+                "result": "282499",
+                "execution_time": 0.01
+            }
+        }
+    },
+    "edges_in": {
+        "name": "Edges - incoming",
+        "files": {
+            "total": {
+                "name": "Total number of incoming edges",
+                "file": "GM005_2.rq",
+                "execute": true,
+                "result": "15111836",
+                "execution_time": 24.99
+            },
+            "min": {
+                "name": "Minimum of incoming edges",
+                "file": "GM005_5.rq",
+                "execute": true,
+                "result": "1",
+                "execution_time": 21.37
+            },
+            "median": {
+                "name": "Median of incoming edges",
+                "file": "GM005_4.rq",
+                "replace_dict": {
+                    "{median_position}": "GM005_2.rq"
+                },
+                "execute": true,
+                "result": "1",
+                "execution_time": 35.6
+            },
+            "max": {
+                "name": "Maximum of incoming edges",
+                "file": "GM005_6.rq",
+                "execute": true,
+                "result": "1121975",
+                "execution_time": 22.57
+            }
+        }
+    },
+    "timestamp": "2025-03-17T15:15"
+}
\ No newline at end of file
diff --git a/reports/metrics/resources.json b/reports/metrics/resources.json
index 0953a73..9e34292 100644
--- a/reports/metrics/resources.json
+++ b/reports/metrics/resources.json
@@ -5,14 +5,14 @@
             "file": "RM001_instances_template.rq",
             "execute": true,
             "result": "724903",
-            "execution_time": 1.31
+            "execution_time": 0.01
         },
         "assertions": {
             "name": "Number of Assertions",
             "file": "RM002_assertions_template.rq",
             "execute": true,
             "result": "724904",
-            "execution_time": 0.02
+            "execution_time": 0.01
         },
         "linkage": {
             "name": "Average Linkage",
@@ -36,7 +36,7 @@
                     "file": "RM004_2_out_edges_min_template.rq",
                     "execute": true,
                     "result": "6",
-                    "execution_time": 1.04
+                    "execution_time": 0.01
                 },
                 "median": {
                     "name": "Median of outgoing edges",
@@ -98,9 +98,9 @@
             "file": "RM006_connectivity_template.rq",
             "execute": true,
             "result": "427594",
-            "execution_time": 4.33
+            "execution_time": 0.01
         },
-        "timestamp": "2025-03-17T13:55",
+        "timestamp": "2025-03-17T15:15",
         "file": "06_dataset.md"
     },
     "publication": {
@@ -109,7 +109,7 @@
             "file": "RM001_instances_template.rq",
             "execute": true,
             "result": "5",
-            "execution_time": 0.02
+            "execution_time": 0.01
         },
         "assertions": {
             "name": "Number of Assertions",
@@ -204,7 +204,7 @@
             "result": "15",
             "execution_time": 0.01
         },
-        "timestamp": "2025-03-17T13:55",
+        "timestamp": "2025-03-17T15:15",
         "file": "07_publication.md"
     },
     "learning_resource": {
@@ -213,7 +213,7 @@
             "file": "RM001_instances_template.rq",
             "execute": true,
             "result": "512",
-            "execution_time": 0.02
+            "execution_time": 0.01
         },
         "assertions": {
             "name": "Number of Assertions",
@@ -280,7 +280,7 @@
                     "file": "RM005_2_in_edges_min_template.rq",
                     "execute": true,
                     "result": "3",
-                    "execution_time": 0.01
+                    "execution_time": 1.02
                 },
                 "median": {
                     "name": "Median of incoming edges",
@@ -308,7 +308,7 @@
             "result": "530",
             "execution_time": 0.01
         },
-        "timestamp": "2025-03-17T13:55",
+        "timestamp": "2025-03-17T15:15",
         "file": "08_learning_resource.md"
     },
     "repository": {
@@ -331,7 +331,7 @@
             "file": "RM003_linkage_template.rq",
             "execute": true,
             "result": "90212",
-            "execution_time": 1.04
+            "execution_time": 1.03
         },
         "edges_out": {
             "name": "Edges - outgoing",
@@ -394,7 +394,7 @@
                     },
                     "execute": true,
                     "result": "6718",
-                    "execution_time": 0.02
+                    "execution_time": 0.01
                 },
                 "max": {
                     "name": "Maximum of incoming edges",
@@ -412,7 +412,7 @@
             "result": "825",
             "execution_time": 0.01
         },
-        "timestamp": "2025-03-17T13:55",
+        "timestamp": "2025-03-17T15:15",
         "file": "09_repository.md"
     },
     "article_lhb": {
@@ -469,7 +469,7 @@
                     "file": "RM004_4_out_edges_max_template.rq",
                     "execute": true,
                     "result": "62",
-                    "execution_time": 1.05
+                    "execution_time": 0.01
                 }
             }
         },
@@ -498,7 +498,7 @@
                     },
                     "execute": true,
                     "result": "2",
-                    "execution_time": 0.02
+                    "execution_time": 0.01
                 },
                 "max": {
                     "name": "Maximum of incoming edges",
@@ -516,7 +516,7 @@
             "result": "592",
             "execution_time": 0.01
         },
-        "timestamp": "2025-03-17T13:55",
+        "timestamp": "2025-03-17T15:15",
         "file": "10_article_lhb.md"
     },
     "standards": {
@@ -539,7 +539,7 @@
             "file": "RM003_linkage_template.rq",
             "execute": true,
             "result": "13.714285714285714",
-            "execution_time": 0.01
+            "execution_time": 1.02
         },
         "edges_out": {
             "name": "Edges - outgoing",
@@ -620,7 +620,7 @@
             "result": "100",
             "execution_time": 0.01
         },
-        "timestamp": "2025-03-17T13:55",
+        "timestamp": "2025-03-17T15:15",
         "file": "11_standards.md"
     },
     "software": {
@@ -724,7 +724,7 @@
             "result": "1103",
             "execution_time": 0.01
         },
-        "timestamp": "2025-03-17T13:55",
+        "timestamp": "2025-03-17T15:15",
         "file": "13_software.md"
     },
     "service": {
@@ -764,7 +764,7 @@
                     "file": "RM004_2_out_edges_min_template.rq",
                     "execute": true,
                     "result": "14",
-                    "execution_time": 0.02
+                    "execution_time": 0.01
                 },
                 "median": {
                     "name": "Median of outgoing edges",
@@ -826,9 +826,9 @@
             "file": "RM006_connectivity_template.rq",
             "execute": true,
             "result": "13",
-            "execution_time": 0.01
+            "execution_time": 1.04
         },
-        "timestamp": "2025-03-17T13:55",
+        "timestamp": "2025-03-17T15:15",
         "file": "14_service.md"
     },
     "data_service": {
@@ -837,7 +837,7 @@
             "file": "RM001_instances_template.rq",
             "execute": true,
             "result": "363632",
-            "execution_time": 0.13
+            "execution_time": 0.01
         },
         "assertions": {
             "name": "Number of Assertions",
@@ -851,7 +851,7 @@
             "file": "RM003_linkage_template.rq",
             "execute": true,
             "result": 0,
-            "execution_time": 0.02
+            "execution_time": 0.01
         },
         "edges_out": {
             "name": "Edges - outgoing",
@@ -868,7 +868,7 @@
                     "file": "RM004_2_out_edges_min_template.rq",
                     "execute": true,
                     "result": "11",
-                    "execution_time": 0.02
+                    "execution_time": 0.01
                 },
                 "median": {
                     "name": "Median of outgoing edges",
@@ -930,9 +930,9 @@
             "file": "RM006_connectivity_template.rq",
             "execute": true,
             "result": "58909",
-            "execution_time": 0.01
+            "execution_time": 0.02
         },
-        "timestamp": "2025-03-17T13:55",
+        "timestamp": "2025-03-17T15:15",
         "file": "15_data_service.md"
     },
     "aggregator": {
@@ -955,7 +955,7 @@
             "file": "RM003_linkage_template.rq",
             "execute": true,
             "result": "187",
-            "execution_time": 0.01
+            "execution_time": 1.05
         },
         "edges_out": {
             "name": "Edges - outgoing",
@@ -972,7 +972,7 @@
                     "file": "RM004_2_out_edges_min_template.rq",
                     "execute": true,
                     "result": "8",
-                    "execution_time": 0.01
+                    "execution_time": 0.02
                 },
                 "median": {
                     "name": "Median of outgoing edges",
@@ -982,7 +982,7 @@
                     },
                     "execute": true,
                     "result": "36",
-                    "execution_time": 0.01
+                    "execution_time": 0.02
                 },
                 "max": {
                     "name": "Maximum of outgoing edges",
@@ -1008,7 +1008,7 @@
                     "file": "RM005_2_in_edges_min_template.rq",
                     "execute": true,
                     "result": "151",
-                    "execution_time": 0.01
+                    "execution_time": 0.02
                 },
                 "median": {
                     "name": "Median of incoming edges",
@@ -1036,7 +1036,7 @@
             "result": "239",
             "execution_time": 0.01
         },
-        "timestamp": "2025-03-17T13:55",
+        "timestamp": "2025-03-17T15:15",
         "file": "16_aggregator.md"
     },
     "person": {
@@ -1045,7 +1045,7 @@
             "file": "RM001_instances_template.rq",
             "execute": true,
             "result": "2180",
-            "execution_time": 0.02
+            "execution_time": 0.01
         },
         "assertions": {
             "name": "Number of Assertions",
@@ -1140,7 +1140,7 @@
             "result": "2",
             "execution_time": 0.01
         },
-        "timestamp": "2025-03-17T13:55",
+        "timestamp": "2025-03-17T15:15",
         "file": "17_person.md"
     },
     "registry": {
@@ -1149,7 +1149,7 @@
             "file": "RM001_instances_template.rq",
             "execute": true,
             "result": "5",
-            "execution_time": 1.04
+            "execution_time": 0.01
         },
         "assertions": {
             "name": "Number of Assertions",
@@ -1244,8 +1244,8 @@
             "result": "15",
             "execution_time": 0.01
         },
-        "timestamp": "2025-03-17T13:55",
+        "timestamp": "2025-03-17T15:15",
         "file": "18_registry.md"
     },
-    "timestamp": "2025-03-17T13:55"
+    "timestamp": "2025-03-17T15:15"
 }
\ No newline at end of file
diff --git a/scripts/kg_analysis/interfaces/general.py b/scripts/kg_analysis/interfaces/general.py
index ded2f56..0db7ca3 100644
--- a/scripts/kg_analysis/interfaces/general.py
+++ b/scripts/kg_analysis/interfaces/general.py
@@ -19,7 +19,7 @@ query_templates = {
     "linkage": {
         "name": "Average Linkage Degree",
         "file": "GM003_2.rq",
-        "execute": False,
+        "execute": True,
     },
     "edges_out": {
         "name": "Edges - outgoing",
@@ -53,23 +53,23 @@ query_templates = {
             "total": {
                 "name": "Total number of incoming edges",
                 "file": "GM005_2.rq",
-                "execute": False,
+                "execute": True,
             },
             "min": {
                 "name": "Minimum of incoming edges",
                 "file": "GM005_5.rq",
-                "execute": False,
+                "execute": True,
             },
             "median": {
                 "name": "Median of incoming edges",
                 "file": "GM005_4.rq",
                 "replace_dict": {"{median_position}": "GM005_2.rq"},
-                "execute": False,
+                "execute": True,
             },
             "max": {
                 "name": "Maximum of incoming edges",
                 "file": "GM005_6.rq",
-                "execute": False,
+                "execute": True,
             },
         },
     },
-- 
GitLab


From 1f6981108b186faede1908e8fffa19f92de986ac Mon Sep 17 00:00:00 2001
From: Ralf Klammer <ralf.klammer@tu-dresden.de>
Date: Mon, 17 Mar 2025 16:34:17 +0100
Subject: [PATCH 23/59] [metrics] exclude 'macros' from navigation

---
 mkdocs.yml | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/mkdocs.yml b/mkdocs.yml
index 7184e25..f6e240f 100644
--- a/mkdocs.yml
+++ b/mkdocs.yml
@@ -18,6 +18,9 @@ plugins:
   - macros:
       module_name: docs/macros/main
 
+exclude_docs:
+  macros
+
 markdown_extensions:
   - admonition
   - pymdownx.details
-- 
GitLab


From b6d1743d920e67bd545e392b36de35447daaa631 Mon Sep 17 00:00:00 2001
From: Ralf Klammer <ralf.klammer@tu-dresden.de>
Date: Mon, 17 Mar 2025 16:34:44 +0100
Subject: [PATCH 24/59] [metrics] unify/simplify macros on table rendering

---
 docs/macros/main.py                           | 246 ++++++++++--------
 .../resource metrics/15_dataservice.md        |   2 +-
 2 files changed, 142 insertions(+), 106 deletions(-)

diff --git a/docs/macros/main.py b/docs/macros/main.py
index ad7ea4c..7d7a58f 100644
--- a/docs/macros/main.py
+++ b/docs/macros/main.py
@@ -5,6 +5,17 @@ from pathlib import Path
 def define_env(env):
     @env.macro
     def include_if_exists(filename, start_line=None, single_line=None):
+        """
+        Includes the content of a file if it exists.
+
+        Args:
+            filename (str): The path to the file.
+            start_line (int, optional): The line number to start including from.
+            single_line (int, optional): The specific line number to include.
+
+        Returns:
+            str: The content of the file or an error message if the file does not exist.
+        """
         try:
             with open(filename, "r") as f:
                 lines = f.readlines()
@@ -14,54 +25,48 @@ def define_env(env):
                     return lines[single_line]
                 return "".join(lines)
         except FileNotFoundError:
-            return f"*Keine Ergebnisse verfügbar. Die Datei `{filename}` wurde nicht gefunden.*"
+            return f"*No results available. The file `{filename}` was not found.*"
         except IndexError:
-            return f"*Fehler: Die angegebene Zeile {start_line} existiert nicht in `{filename}`.*"
+            return f"*Error: The specified line {start_line} does not exist in `{filename}`.*"
 
     @env.macro
     def include_template(template_path, resource_type, **kwargs):
         """
-        Includes a template file and replaces {resource_type} with the given value
+        Includes a template file and replaces {resource_type} with the given value.
 
         Args:
-            template_path (str): Path to the template file
-            resource_type (str): The resource type URI to inject
+            template_path (str): Path to the template file.
+            resource_type (str): The resource type URI to inject.
+
+        Returns:
+            str: The content of the template with the resource type injected.
         """
         content = include_if_exists(template_path, **kwargs)
         if content:
             return content.replace("{resource_type}", resource_type)
         return ""
 
-    def render_resource(resource_data, output, resource_type=None):
-        """Helper function to render a single resource"""
-        for query_data in resource_data.values():
-            if "file" in query_data:
-                if resource_type:
-                    output.append(
-                        f"| {resource_type} | {query_data['name']} | {query_data['file']} | {query_data['result']} |"
-                    )
-                else:
-                    output.append(
-                        f"| {query_data['name']} | [{query_data['file']}](#{query_data['file']}) | {query_data['result']} |"
-                    )
-            elif "files" in query_data:
-                if resource_type:
-                    output.append(f"| {resource_type} | **{query_data['name']}** | | |")
-                else:
-                    output.append(f"| **{query_data['name']}** | | |")
-                    for sub_query_data in query_data["files"].values():
-                        if resource_type:
-                            output.append(
-                                f"| {resource_type} | {sub_query_data['name']} | {sub_query_data['file']} | {sub_query_data['result']} |"
-                            )
-                        else:
-                            output.append(
-                                f"| {sub_query_data['name']} | [{sub_query_data['file']}](#{sub_query_data['file']}) | {sub_query_data['result']} |"
-                            )
+    def render_resource(resource_data, output):
+        """
+        Helper function to render a single resource.
+
+        Args:
+            resource_data (dict): The data of the resource.
+            output (list): The list to append the rendered resource to.
+        """
+        pass
 
     @env.macro
     def resource_metrics_table(resource_type=None):
-        """Renders metrics as markdown table for one or all resource types"""
+        """
+        Renders metrics as a markdown table for one or all resource types.
+
+        Args:
+            resource_type (str, optional): The specific resource type to render metrics for.
+
+        Returns:
+            str: The rendered markdown table or an error message.
+        """
         try:
             metrics_file = Path("reports/metrics/resources.json")
             with open(metrics_file, "r", encoding="utf-8") as f:
@@ -70,36 +75,43 @@ def define_env(env):
             return f"*Error loading metrics: {e}*"
 
         output = []
-        timestamp = resources.get("timestamp", "unknown")
-        output.append(f"*Last updated: {timestamp}*\n")
 
-        if resource_type:
-            # Render single resource
-            if resource_type not in resources:
-                return f"*No metrics found for {resource_type}*"
+        if resource_type not in resources:
+            return f"*No metrics found for {resource_type}*"
 
-            output.append("| Metric | Query (file) | Result |")
-            output.append("|--------|------|--------|")
-            render_resource(resources[resource_type], output)
-        else:
-            # Render all resources
-            output.append("| Resource Type | Metric | Query (file) | Result |")
-            output.append("|--------------|--------|------|--------|")
-            for res_type, resource_data in resources.items():
-                if res_type != "timestamp":
-                    render_resource(resource_data, output, res_type)
+        output.append("| Metric | Query (file) | Result |")
+        output.append("|--------|------|--------|")
+
+        for query_data in resources[resource_type].values():
+            if "file" in query_data:
+                output.append(
+                    f"| {query_data['name']} | [{query_data['file']}](#{query_data['file']}) | {query_data['result']} |"
+                )
+            elif "files" in query_data:
+                output.append(f"| **{query_data['name']}** | | |")
+                for sub_query_data in query_data["files"].values():
+                    output.append(
+                        f"| {sub_query_data['name']} | [{sub_query_data['file']}](#{sub_query_data['file']}) | {sub_query_data['result']} |"
+                    )
+
+        output.append(f"\n*Last updated: {resources.get('timestamp', '-')}*")
 
         return "\n".join(output)
 
     @env.macro
     def resources_metrics_table():
-        """Rendert eine einfache Übersichtstabelle der Resource-Metriken"""
+        """
+        Renders a simple overview table of resource metrics.
+
+        Returns:
+            str: The rendered markdown table or an error message.
+        """
         try:
             metrics_file = Path("reports/metrics/resources.json")
             with open(metrics_file, "r", encoding="utf-8") as f:
                 resources = json.load(f)
         except (FileNotFoundError, json.JSONDecodeError) as e:
-            return f"*Fehler beim Laden der Metriken: {e}*"
+            return f"*Error loading metrics: {e}*"
 
         output = []
 
@@ -131,81 +143,105 @@ def define_env(env):
         for row in rows:
             output.append(row)
 
-        output.append(f"\n*Last updated: {resources.get("timestamp", "-")}*")
+        output.append(f"\n*Last updated: {resources.get('timestamp', '-')}*")
         return "\n".join(output)
 
-    @env.macro
-    def render_metric_table(metric_key):
+    def render_metrics_table(metrics_data, table_type="general"):
         """
-        Renders a specific metric as markdown table.
+        Central function to render metric tables.
 
         Args:
-            metric_key (str): Key of the metric to render (e.g. 'instances', 'edges_out')
-        """
-        try:
-            metrics_file = Path("reports/metrics/general.json")
-            with open(metrics_file, "r", encoding="utf-8") as f:
-                metrics = json.load(f)
-        except (FileNotFoundError, json.JSONDecodeError) as e:
-            return f"*Error loading metrics: {e}*"
-
-        if metric_key not in metrics:
-            return f"*No data found for metric: {metric_key}*"
+            metrics_data (dict): The JSON data with the metrics.
+            table_type (str): Type of the table ('general', 'resource', 'overview').
 
+        Returns:
+            str: The rendered markdown table.
+        """
         output = []
-        timestamp = metrics.get("timestamp", "unknown")
+        timestamp = metrics_data.get("timestamp", "unknown")
         output.append(f"*Last updated: {timestamp}*\n")
 
-        output.append("| Metric | Query (file) | Result |")
-        output.append("|--------|------|--------|")
+        if table_type == "overview":
+            resource_types = sorted(
+                [rt for rt in metrics_data.keys() if rt != "timestamp"]
+            )
+            metric_names = []
+            rows = []
+
+            first_rt = metrics_data[resource_types[0]]
+            for query in first_rt.get("queries", {}).values():
+                if "name" in query:
+                    metric_names.append(query["name"])
+
+            output.append("| Resource Type | " + " | ".join(metric_names) + " |")
+            output.append("|" + "|".join(["---"] * (len(metric_names) + 1)) + "|")
+
+            for rt in resource_types:
+                row = [rt]
+                queries = metrics_data[rt].get("queries", {})
+                for metric in metric_names:
+                    result = "-"
+                    for q in queries.values():
+                        if q.get("name") == metric:
+                            result = str(q.get("result", "-"))
+                            break
+                    row.append(result)
+                output.append("| " + " | ".join(row) + " |")
 
-        metric_data = metrics[metric_key]
-        if "files" in metric_data:
-            # Handle composite metrics (like edge statistics)
-            output.append(f"| **{metric_data['name']}** | | |")
-            for sub_query in metric_data["files"].values():
-                output.append(
-                    f"| {sub_query['name']} | [{sub_query['file']}](#{sub_query['file']}) | {sub_query.get('result', '-')} |"
-                )
         else:
-            # Handle simple metrics
-            output.append(
-                f"| {metric_data['name']} | [{metric_data['file']}](#{metric_data['file']}) | {metric_data.get('result', '-')} |"
-            )
+            output.append("| Metric | Query | Result |")
+            output.append("|--------|-------|--------|")
+
+            for metric_key, metric_data in metrics_data.items():
+                if metric_key == "timestamp":
+                    continue
+
+                if isinstance(metric_data, dict):
+                    if "files" in metric_data:
+                        output.append(f"| **{metric_data['name']}** | | |")
+                        for sub_query in metric_data["files"].values():
+                            output.append(
+                                f"| {sub_query['name']} | [{sub_query['file']}](#{sub_query['file']}) | {sub_query.get('result', '-')} |"
+                            )
+                    elif "name" in metric_data:
+                        output.append(
+                            f"| {metric_data['name']} | [{metric_data['file']}](#{metric_data['file']}) | {metric_data.get('result', '-')} |"
+                        )
 
         return "\n".join(output)
 
     @env.macro
     def general_metrics_table():
-        """Renders a complete overview table of all general metrics"""
+        """
+        Renders the overview table of general metrics.
+
+        Returns:
+            str: The rendered markdown table or an error message.
+        """
         try:
-            metrics_file = Path("reports/metrics/general.json")
-            with open(metrics_file, "r", encoding="utf-8") as f:
-                metrics = json.load(f)
+            with open(Path("reports/metrics/general.json"), "r") as f:
+                return render_metrics_table(json.load(f), "general")
         except (FileNotFoundError, json.JSONDecodeError) as e:
             return f"*Error loading metrics: {e}*"
 
-        output = []
-
-        output.append("| Metric | File | Result |")
-        output.append("|--------|------|--------|")
+    @env.macro
+    def render_metric_table(metric_key):
+        """
+        Renders a single metric.
 
-        for metric_key, metric_data in metrics.items():
-            if metric_key == "timestamp":
-                continue
+        Args:
+            metric_key (str): The key of the metric to render.
 
-            if "files" in metric_data:
-                # Handle composite metrics
-                output.append(f"| **{metric_data['name']}** | | |")
-                for sub_query in metric_data["files"].values():
-                    output.append(
-                        f"| {sub_query['name']} | {sub_query['file']} | {sub_query.get('result')} |"
-                    )
-            else:
-                # Handle simple metrics
-                output.append(
-                    f"| {metric_data['name']} | {metric_data['file']} | {metric_data.get('result')} |"
+        Returns:
+            str: The rendered markdown table or an error message.
+        """
+        try:
+            with open(Path("reports/metrics/general.json"), "r") as f:
+                metrics = json.load(f)
+                if metric_key not in metrics:
+                    return f"*No data for metric: {metric_key}*"
+                return render_metrics_table(
+                    {metric_key: metrics[metric_key]}, "general"
                 )
-
-        output.append(f"\n*Last updated: {metrics.get("timestamp", "-")}*")
-        return "\n".join(output)
+        except (FileNotFoundError, json.JSONDecodeError) as e:
+            return f"*Error loading metrics: {e}*"
diff --git a/docs/metrics/resource metrics/15_dataservice.md b/docs/metrics/resource metrics/15_dataservice.md
index a102d74..c43d8c1 100644
--- a/docs/metrics/resource metrics/15_dataservice.md	
+++ b/docs/metrics/resource metrics/15_dataservice.md	
@@ -1,4 +1,4 @@
 {% from "macros/resource_metrics.md" import resource_metrics with context %}
 # Data Service
 
-{{ resource_metrics("dataservice", "<http://www.w3.org/ns/dcat#DataService>") }}
+{{ resource_metrics("data_service", "<http://www.w3.org/ns/dcat#DataService>") }}
-- 
GitLab


From 29b778adc46e6a640a88d0456970fdb18a479af8 Mon Sep 17 00:00:00 2001
From: Ralf Klammer <ralf.klammer@tu-dresden.de>
Date: Mon, 17 Mar 2025 16:35:21 +0100
Subject: [PATCH 25/59] [metrics] use 8Mio for avarage linkage

---
 queries/metrics/GM003_2.rq | 10 ++++++----
 1 file changed, 6 insertions(+), 4 deletions(-)

diff --git a/queries/metrics/GM003_2.rq b/queries/metrics/GM003_2.rq
index de8c8e1..2111a12 100644
--- a/queries/metrics/GM003_2.rq
+++ b/queries/metrics/GM003_2.rq
@@ -2,8 +2,11 @@
 # (i.e.: how many assertions per entity does the graph contain)
 #
 # Explanation:
-#   This is an alternative version (esp. for testing purposes)
-#   as it enables a limitation of analysed entities
+#   This is an alternative version as it enables a limitation of
+#   analysed entities.
+#   We have found that starting with a limit of 8,000,000 entities,
+#   the results begin to provide meaningful outcome, which does not change
+#   significantly with a higher limit.
 
 SELECT (AVG(?totalDegree) as ?avgLinkageDegree)
 WHERE {
@@ -15,8 +18,7 @@ WHERE {
         WHERE {
           { ?entity ?p ?o } UNION { ?s ?p ?entity }
         }
-        LIMIT 1000  # Process 1000 entities at a time
-        OFFSET 0    # Start with first batch, increment for next batches
+        LIMIT 8000000  # Process limited entities at a time
       }
       {
         SELECT ?entity (COUNT(*) as ?outDegree)
-- 
GitLab


From cf90b917f251e8e552e8a0a1b7c38da4fe436e9d Mon Sep 17 00:00:00 2001
From: Ralf Klammer <ralf.klammer@tu-dresden.de>
Date: Mon, 17 Mar 2025 16:35:50 +0100
Subject: [PATCH 26/59] [metrics] forget main.pyc

---
 docs/macros/__pycache__/main.cpython-312.pyc | Bin 1290 -> 12511 bytes
 1 file changed, 0 insertions(+), 0 deletions(-)

diff --git a/docs/macros/__pycache__/main.cpython-312.pyc b/docs/macros/__pycache__/main.cpython-312.pyc
index ae0eaebbb09fdee392a5b5d374f2647080079500..538bfb47a654264f2a795d752d539b09ebbf3cb6 100644
GIT binary patch
literal 12511
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

delta 608
zcmcbg*u@oenwOW0fq{Xcblb)B4NMFSk3k$5W`r_6gOp5XNMUGUh+?Q@)MQGA$$%6v
zGcYiK@MkRs28QVj6X%JEq%fs0wlJ;+$ucl7)G}5wXfjVOVvJ*CntYK_QkX%CVKvl*
zWTq4b5S;v%QCb+xTFnH}#>f!NkOERKHd$7jg9ENGkV%%2VRAl`Bs+5r!)%5WmdX80
zlJ%@LjI$Y1*uWy}b6Jy_A{l}i${8w{BN@sWH97olF&5ooDAi<OV9@eT&CE+xa4kwt
zP0GtGE>2Y_OD#$}yeB;=u}DwBB{NmQC9xzmQz4;BB0-_Nv?wK2AulsIqeLM+HLWx+
zB{fe^t4dDGEj1%2wa5yrJ~1y{J~cfxDK#%uAu2U9Csm<JR3Wt@v$!NPwWtKFd-FaP
zHbw!E&x#K*FfcSQd|=_`OrCs&wZSrp@rJ0~j*1;w7d#R!2q#|RNxZ`^`H6v%Gnw&*
zsPP7`4K^3-LoS5IUI>Z15SMsCIO!5k(g$Wn&g99xY=QNfOn!cvjJH^G6O)VbHJNTP
zr{<Lv2{JG+6oV{NP$&{$U|_h#m6Dp4nU@+5k`!TJU;qVvu@u-LH&{8^BO4<>G4Ze_
zGk#`fVB`M?VS-}8Pm{3-q<$qskthQL1K6-%95%W6DWy57c17Y0(onOw85kHoFf%eT
NerDohOlAbD1^^iLmxTZT

-- 
GitLab


From 505e8ea29247c1a207e741a8e6b71ed7362ea831 Mon Sep 17 00:00:00 2001
From: Ralf Klammer <ralf.klammer@tu-dresden.de>
Date: Mon, 17 Mar 2025 16:36:23 +0100
Subject: [PATCH 27/59] [rmetrics] remove former reports

---
 reports/metrics/004.txt   | 4 ----
 reports/metrics/005.txt   | 4 ----
 reports/metrics/GM001.txt | 3 ---
 reports/metrics/GM002.txt | 3 ---
 reports/metrics/GM003.txt | 3 ---
 reports/metrics/GM004.txt | 4 ----
 reports/metrics/GM005.txt | 4 ----
 7 files changed, 25 deletions(-)
 delete mode 100644 reports/metrics/004.txt
 delete mode 100644 reports/metrics/005.txt
 delete mode 100644 reports/metrics/GM001.txt
 delete mode 100644 reports/metrics/GM002.txt
 delete mode 100644 reports/metrics/GM003.txt
 delete mode 100644 reports/metrics/GM004.txt
 delete mode 100644 reports/metrics/GM005.txt

diff --git a/reports/metrics/004.txt b/reports/metrics/004.txt
deleted file mode 100644
index 6b57c75..0000000
--- a/reports/metrics/004.txt
+++ /dev/null
@@ -1,4 +0,0 @@
-2024-03-12 15:20
-- Total Edges: 6705836
-- Median Position: 3352918
-- Median Outgoing Edges: 2
\ No newline at end of file
diff --git a/reports/metrics/005.txt b/reports/metrics/005.txt
deleted file mode 100644
index d337df0..0000000
--- a/reports/metrics/005.txt
+++ /dev/null
@@ -1,4 +0,0 @@
-2024-03-12 15:20
-- Total Edges: 15111836
-- Median Position: 7555918
-- Median Outgoing Edges: 1
\ No newline at end of file
diff --git a/reports/metrics/GM001.txt b/reports/metrics/GM001.txt
deleted file mode 100644
index 4778d12..0000000
--- a/reports/metrics/GM001.txt
+++ /dev/null
@@ -1,3 +0,0 @@
-2025-03-13T13:57
-- Instance count: 1252089
-- Execution time: 0.05 seconds
\ No newline at end of file
diff --git a/reports/metrics/GM002.txt b/reports/metrics/GM002.txt
deleted file mode 100644
index f83346c..0000000
--- a/reports/metrics/GM002.txt
+++ /dev/null
@@ -1,3 +0,0 @@
-2025-03-13T13:57
-- Assertions count: 40786353
-- Execution time: 0.04 seconds
\ No newline at end of file
diff --git a/reports/metrics/GM003.txt b/reports/metrics/GM003.txt
deleted file mode 100644
index 4680c25..0000000
--- a/reports/metrics/GM003.txt
+++ /dev/null
@@ -1,3 +0,0 @@
-2025-03-13T13:57
-- Assertions count: 3.894342748611638
-- Execution time: 0.01 seconds
\ No newline at end of file
diff --git a/reports/metrics/GM004.txt b/reports/metrics/GM004.txt
deleted file mode 100644
index 7dc2b50..0000000
--- a/reports/metrics/GM004.txt
+++ /dev/null
@@ -1,4 +0,0 @@
-2025-03-13T13:57
-- Total count: 6705836
-- Median: None
-- Execution time: 12.72 seconds
\ No newline at end of file
diff --git a/reports/metrics/GM005.txt b/reports/metrics/GM005.txt
deleted file mode 100644
index 2c28f94..0000000
--- a/reports/metrics/GM005.txt
+++ /dev/null
@@ -1,4 +0,0 @@
-2025-03-13T13:57
-- Total count: 15111836
-- Median: 1
-- Execution time: 0.02 seconds
\ No newline at end of file
-- 
GitLab


From b202e24e99a6a30353e3403f1cd5beae790cc120 Mon Sep 17 00:00:00 2001
From: Ralf Klammer <ralf.klammer@tu-dresden.de>
Date: Tue, 18 Mar 2025 11:05:37 +0100
Subject: [PATCH 28/59] [metrics]fix: Show all metrics in
 'resources_metrics_table'

---
 docs/macros/main.py | 23 ++++++++++++++++-------
 1 file changed, 16 insertions(+), 7 deletions(-)

diff --git a/docs/macros/main.py b/docs/macros/main.py
index 7d7a58f..cf59d4e 100644
--- a/docs/macros/main.py
+++ b/docs/macros/main.py
@@ -122,24 +122,33 @@ def define_env(env):
 
         metric_names = []
         rows = []
+        # Create a row for each resource type
         for resource_name in ordered_resource_names:
             resource = resources[resource_name]
-            row = f"| {resource_name.upper()} |"
+            row = [resource_name.upper()]
             for metric in resource.values():
+                # Skip the timestamp (or other non-dict values)
                 if isinstance(metric, str):
                     continue
+                # Check if the metric has sub-metrics
                 elif "files" in metric:
                     for sub_metric in metric["files"].values():
+                        # Append the name of the sub-metric
                         if sub_metric["name"] not in metric_names:
                             metric_names.append(sub_metric["name"])
-                        row += f" {sub_metric.get('result', '-')} |"
-                elif metric["name"] not in metric_names:
-                    row += f" {metric.get('result', '-')} |"
-                    metric_names.append(metric["name"])
-            rows.append(row)
-
+                        # Append the result of the sub-metric
+                        row.append(sub_metric.get("result", "-") or "-")
+                else:
+                    # Append the result of the metric
+                    row.append(metric.get("result", "-") or "-")
+                    if metric["name"] not in metric_names:
+                        metric_names.append(metric["name"])
+            rows.append("|".join(row))
+
+        # Create the header row
         output.append(f"| Resource type | {' | '.join(metric_names)} |")
         output.append(f"| --- |{' | '.join(['---' for m in metric_names])} |")
+        # Append the rows
         for row in rows:
             output.append(row)
 
-- 
GitLab


From 83922d1af764023b9a78c9c19d3dc821a1c31a2f Mon Sep 17 00:00:00 2001
From: Ralf Klammer <ralf.klammer@tu-dresden.de>
Date: Tue, 18 Mar 2025 13:16:54 +0100
Subject: [PATCH 29/59] [metrics] ignore '__pycache__'

---
 .gitignore                                   |   1 +
 docs/macros/__pycache__/main.cpython-312.pyc | Bin 12511 -> 5974 bytes
 2 files changed, 1 insertion(+)

diff --git a/.gitignore b/.gitignore
index 854d509..41dd612 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1 +1,2 @@
 *venv*
+docs/macros/__pycache__/*
\ No newline at end of file
diff --git a/docs/macros/__pycache__/main.cpython-312.pyc b/docs/macros/__pycache__/main.cpython-312.pyc
index 538bfb47a654264f2a795d752d539b09ebbf3cb6..f5171ef96bc57fca46195c8c6b61980147820c96 100644
GIT binary patch
delta 3045
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

literal 12511
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

-- 
GitLab


From 71614bac3885095c10fd1960b711a6eecc80b724 Mon Sep 17 00:00:00 2001
From: Ralf Klammer <ralf.klammer@tu-dresden.de>
Date: Tue, 18 Mar 2025 13:17:50 +0100
Subject: [PATCH 30/59] [metrics] Add table_renderer/MetricsTableRenderer

---
 docs/macros/main.py                           | 208 ++----------------
 docs/macros/resource_metrics.md               |   2 +-
 docs/macros/table_renderer.py                 | 148 +++++++++++++
 docs/metrics/general metrics/01_instances.md  |   2 +-
 docs/metrics/general metrics/02_assertions.md |   2 +-
 .../general metrics/03_linkage_degree.md      |   2 +-
 .../general metrics/04_outgoing_edges.md      |   2 +-
 .../general metrics/05_incoming_edges.md      |   2 +-
 docs/metrics/index.md                         |   4 +-
 9 files changed, 180 insertions(+), 192 deletions(-)
 create mode 100644 docs/macros/table_renderer.py

diff --git a/docs/macros/main.py b/docs/macros/main.py
index cf59d4e..64a249d 100644
--- a/docs/macros/main.py
+++ b/docs/macros/main.py
@@ -1,6 +1,12 @@
 import json
+import os
+import sys
 from pathlib import Path
 
+sys.path.append(os.path.dirname(os.path.abspath(__file__)))
+
+from table_renderer import MetricsTableRenderer
+
 
 def define_env(env):
     @env.macro
@@ -46,211 +52,45 @@ def define_env(env):
             return content.replace("{resource_type}", resource_type)
         return ""
 
-    def render_resource(resource_data, output):
-        """
-        Helper function to render a single resource.
-
-        Args:
-            resource_data (dict): The data of the resource.
-            output (list): The list to append the rendered resource to.
-        """
-        pass
-
     @env.macro
-    def resource_metrics_table(resource_type=None):
-        """
-        Renders metrics as a markdown table for one or all resource types.
-
-        Args:
-            resource_type (str, optional): The specific resource type to render metrics for.
-
-        Returns:
-            str: The rendered markdown table or an error message.
-        """
+    def metrics_table_single_resource(resource_type=None):
         try:
-            metrics_file = Path("reports/metrics/resources.json")
-            with open(metrics_file, "r", encoding="utf-8") as f:
-                resources = json.load(f)
+            with open(
+                Path("reports/metrics/resources.json"), "r", encoding="utf-8"
+            ) as f:
+                renderer = MetricsTableRenderer(json.load(f))
+                return renderer.render("resource", resource_type)
         except (FileNotFoundError, json.JSONDecodeError) as e:
             return f"*Error loading metrics: {e}*"
 
-        output = []
-
-        if resource_type not in resources:
-            return f"*No metrics found for {resource_type}*"
-
-        output.append("| Metric | Query (file) | Result |")
-        output.append("|--------|------|--------|")
-
-        for query_data in resources[resource_type].values():
-            if "file" in query_data:
-                output.append(
-                    f"| {query_data['name']} | [{query_data['file']}](#{query_data['file']}) | {query_data['result']} |"
-                )
-            elif "files" in query_data:
-                output.append(f"| **{query_data['name']}** | | |")
-                for sub_query_data in query_data["files"].values():
-                    output.append(
-                        f"| {sub_query_data['name']} | [{sub_query_data['file']}](#{sub_query_data['file']}) | {sub_query_data['result']} |"
-                    )
-
-        output.append(f"\n*Last updated: {resources.get('timestamp', '-')}*")
-
-        return "\n".join(output)
-
     @env.macro
-    def resources_metrics_table():
-        """
-        Renders a simple overview table of resource metrics.
-
-        Returns:
-            str: The rendered markdown table or an error message.
-        """
+    def metrics_table_overview_resource():
         try:
-            metrics_file = Path("reports/metrics/resources.json")
-            with open(metrics_file, "r", encoding="utf-8") as f:
-                resources = json.load(f)
+            with open(
+                Path("reports/metrics/resources.json"), "r", encoding="utf-8"
+            ) as f:
+                renderer = MetricsTableRenderer(json.load(f))
+                return renderer.render("resource_overview")
         except (FileNotFoundError, json.JSONDecodeError) as e:
             return f"*Error loading metrics: {e}*"
 
-        output = []
-
-        resource_names = [
-            key for key, resource in resources.items() if isinstance(resource, dict)
-        ]
-        ordered_resource_names = sorted(resource_names)
-
-        metric_names = []
-        rows = []
-        # Create a row for each resource type
-        for resource_name in ordered_resource_names:
-            resource = resources[resource_name]
-            row = [resource_name.upper()]
-            for metric in resource.values():
-                # Skip the timestamp (or other non-dict values)
-                if isinstance(metric, str):
-                    continue
-                # Check if the metric has sub-metrics
-                elif "files" in metric:
-                    for sub_metric in metric["files"].values():
-                        # Append the name of the sub-metric
-                        if sub_metric["name"] not in metric_names:
-                            metric_names.append(sub_metric["name"])
-                        # Append the result of the sub-metric
-                        row.append(sub_metric.get("result", "-") or "-")
-                else:
-                    # Append the result of the metric
-                    row.append(metric.get("result", "-") or "-")
-                    if metric["name"] not in metric_names:
-                        metric_names.append(metric["name"])
-            rows.append("|".join(row))
-
-        # Create the header row
-        output.append(f"| Resource type | {' | '.join(metric_names)} |")
-        output.append(f"| --- |{' | '.join(['---' for m in metric_names])} |")
-        # Append the rows
-        for row in rows:
-            output.append(row)
-
-        output.append(f"\n*Last updated: {resources.get('timestamp', '-')}*")
-        return "\n".join(output)
-
-    def render_metrics_table(metrics_data, table_type="general"):
-        """
-        Central function to render metric tables.
-
-        Args:
-            metrics_data (dict): The JSON data with the metrics.
-            table_type (str): Type of the table ('general', 'resource', 'overview').
-
-        Returns:
-            str: The rendered markdown table.
-        """
-        output = []
-        timestamp = metrics_data.get("timestamp", "unknown")
-        output.append(f"*Last updated: {timestamp}*\n")
-
-        if table_type == "overview":
-            resource_types = sorted(
-                [rt for rt in metrics_data.keys() if rt != "timestamp"]
-            )
-            metric_names = []
-            rows = []
-
-            first_rt = metrics_data[resource_types[0]]
-            for query in first_rt.get("queries", {}).values():
-                if "name" in query:
-                    metric_names.append(query["name"])
-
-            output.append("| Resource Type | " + " | ".join(metric_names) + " |")
-            output.append("|" + "|".join(["---"] * (len(metric_names) + 1)) + "|")
-
-            for rt in resource_types:
-                row = [rt]
-                queries = metrics_data[rt].get("queries", {})
-                for metric in metric_names:
-                    result = "-"
-                    for q in queries.values():
-                        if q.get("name") == metric:
-                            result = str(q.get("result", "-"))
-                            break
-                    row.append(result)
-                output.append("| " + " | ".join(row) + " |")
-
-        else:
-            output.append("| Metric | Query | Result |")
-            output.append("|--------|-------|--------|")
-
-            for metric_key, metric_data in metrics_data.items():
-                if metric_key == "timestamp":
-                    continue
-
-                if isinstance(metric_data, dict):
-                    if "files" in metric_data:
-                        output.append(f"| **{metric_data['name']}** | | |")
-                        for sub_query in metric_data["files"].values():
-                            output.append(
-                                f"| {sub_query['name']} | [{sub_query['file']}](#{sub_query['file']}) | {sub_query.get('result', '-')} |"
-                            )
-                    elif "name" in metric_data:
-                        output.append(
-                            f"| {metric_data['name']} | [{metric_data['file']}](#{metric_data['file']}) | {metric_data.get('result', '-')} |"
-                        )
-
-        return "\n".join(output)
-
     @env.macro
-    def general_metrics_table():
-        """
-        Renders the overview table of general metrics.
-
-        Returns:
-            str: The rendered markdown table or an error message.
-        """
+    def metrics_table_overview_general():
         try:
             with open(Path("reports/metrics/general.json"), "r") as f:
-                return render_metrics_table(json.load(f), "general")
+                renderer = MetricsTableRenderer(json.load(f))
+                return renderer.render("general")
         except (FileNotFoundError, json.JSONDecodeError) as e:
             return f"*Error loading metrics: {e}*"
 
     @env.macro
-    def render_metric_table(metric_key):
-        """
-        Renders a single metric.
-
-        Args:
-            metric_key (str): The key of the metric to render.
-
-        Returns:
-            str: The rendered markdown table or an error message.
-        """
+    def metrics_table_single_general(metric_key):
         try:
             with open(Path("reports/metrics/general.json"), "r") as f:
                 metrics = json.load(f)
                 if metric_key not in metrics:
                     return f"*No data for metric: {metric_key}*"
-                return render_metrics_table(
-                    {metric_key: metrics[metric_key]}, "general"
-                )
+                renderer = MetricsTableRenderer({metric_key: metrics[metric_key]})
+                return renderer.render("general")
         except (FileNotFoundError, json.JSONDecodeError) as e:
             return f"*Error loading metrics: {e}*"
diff --git a/docs/macros/resource_metrics.md b/docs/macros/resource_metrics.md
index ce5130b..42bfcd3 100644
--- a/docs/macros/resource_metrics.md
+++ b/docs/macros/resource_metrics.md
@@ -3,7 +3,7 @@
 Resource type: {{resource_type_uri}}
 
 ## Results
-{{ resource_metrics_table(resource_type) }}
+{{ metrics_table_single_resource(resource_type) }}
 
 ## Basic Metrics
 
diff --git a/docs/macros/table_renderer.py b/docs/macros/table_renderer.py
new file mode 100644
index 0000000..da8ea24
--- /dev/null
+++ b/docs/macros/table_renderer.py
@@ -0,0 +1,148 @@
+class MetricsTableRenderer:
+    """Class for rendering metric tables in various formats."""
+
+    def __init__(self, data):
+        """
+        Initializes the renderer with metric data.
+
+        Args:
+            data (dict): The JSON data containing the metrics
+        """
+        self.data = data
+        self.timestamp = data.get("timestamp", "unknown")
+        self.output = []
+
+    def render(self, table_type="general", resource_type=None):
+        """
+        Renders the table in the desired format.
+
+        Args:
+            table_type (str): Type of table ('general', 'resource', 'general_overview', 'resource_overview')
+            resource_type (str, optional): Specific resource type for resource tables
+
+        Returns:
+            str: The rendered markdown table
+        """
+        self.output = []
+
+        # Dictionary mapping table types to their corresponding rendering methods
+        render_methods = {
+            "resource_overview": self._render_resource_overview,
+            "resource": lambda: self._render_resource(resource_type),
+            "general_overview": self._render_general_overview,
+            "general": self._render_general,
+        }
+
+        # Get the appropriate rendering method based on the table type
+        render_method = render_methods.get(table_type, self._render_general)
+        render_method()
+
+        # Append the timestamp to the output
+        self.output.append(f"\n*Last updated: {self.timestamp}*")
+        return "\n".join(self.output)
+
+    def _render_resource_overview(self):
+        """Renders the resource overview table."""
+        # Get the names of all resources
+        resource_names = [
+            key for key, resource in self.data.items() if isinstance(resource, dict)
+        ]
+        ordered_resource_names = sorted(resource_names)
+
+        metric_names = []
+        rows = []
+
+        # Iterate over each resource to collect metric names and results
+        for resource_name in ordered_resource_names:
+            resource = self.data[resource_name]
+            row = [resource_name.upper()]
+            for metric in resource.values():
+                if isinstance(metric, str):
+                    continue
+                elif "files" in metric:
+                    for sub_metric in metric["files"].values():
+                        if sub_metric["name"] not in metric_names:
+                            metric_names.append(sub_metric["name"])
+                        row.append(sub_metric.get("result", "-") or "-")
+                else:
+                    row.append(metric.get("result", "-") or "-")
+                    if metric["name"] not in metric_names:
+                        metric_names.append(metric["name"])
+            rows.append("| " + " | ".join(row) + " |")
+
+        # Construct the table header and rows
+        self.output.append(f"| Resource type | {' | '.join(metric_names)} |")
+        self.output.append(f"| --- |{' | '.join(['---' for _ in metric_names])} |")
+        self.output.extend(rows)
+
+    def _render_resource(self, resource_type):
+        """Renders the resource-specific table."""
+        if resource_type not in self.data:
+            return f"*No metrics found for {resource_type}*"
+
+        self.output.append("| Metric | Query (file) | Result |")
+        self.output.append("|--------|------|--------|")
+
+        # Iterate over each query data for the specified resource type
+        for query_data in self.data[resource_type].values():
+            if "file" in query_data:
+                self.output.append(
+                    f"| {query_data['name']} | [{query_data['file']}](#{query_data['file']}) | {query_data['result']} |"
+                )
+            elif "files" in query_data:
+                self.output.append(f"| **{query_data['name']}** | | |")
+                for sub_query_data in query_data["files"].values():
+                    self.output.append(
+                        f"| {sub_query_data['name']} | [{sub_query_data['file']}](#{sub_query_data['file']}) | {sub_query_data['result']} |"
+                    )
+
+    def _render_general_overview(self):
+        """Renders the general overview table."""
+        # Get the sorted list of resource types
+        resource_types = sorted([rt for rt in self.data.keys() if rt != "timestamp"])
+        metric_names = []
+
+        # Collect metric names from the first resource type
+        first_rt = self.data[resource_types[0]]
+        for query in first_rt.get("queries", {}).values():
+            if "name" in query:
+                metric_names.append(query["name"])
+
+        # Construct the table header
+        self.output.append("| Resource Type | " + " | ".join(metric_names) + " |")
+        self.output.append("|" + "|".join(["---"] * (len(metric_names) + 1)) + "|")
+
+        # Iterate over each resource type to collect results
+        for rt in resource_types:
+            row = [rt]
+            queries = self.data[rt].get("queries", {})
+            for metric in metric_names:
+                result = "-"
+                for q in queries.values():
+                    if q.get("name") == metric:
+                        result = str(q.get("result", "-"))
+                        break
+                row.append(result)
+            self.output.append("| " + " | ".join(row) + " |")
+
+    def _render_general(self):
+        """Renders the general table."""
+        self.output.append("| Metric | Query | Result |")
+        self.output.append("|--------|-------|--------|")
+
+        # Iterate over each metric data to construct the table rows
+        for metric_key, metric_data in self.data.items():
+            if metric_key == "timestamp":
+                continue
+
+            if isinstance(metric_data, dict):
+                if "files" in metric_data:
+                    self.output.append(f"| **{metric_data['name']}** | | |")
+                    for sub_query in metric_data["files"].values():
+                        self.output.append(
+                            f"| {sub_query['name']} | {sub_query['file']} | {sub_query.get('result', '-')} |"
+                        )
+                elif "name" in metric_data:
+                    self.output.append(
+                        f"| {metric_data['name']} | {metric_data['file']} | {metric_data.get('result', '-')} |"
+                    )
diff --git a/docs/metrics/general metrics/01_instances.md b/docs/metrics/general metrics/01_instances.md
index 150c6be..e37a978 100644
--- a/docs/metrics/general metrics/01_instances.md	
+++ b/docs/metrics/general metrics/01_instances.md	
@@ -9,7 +9,7 @@ The metric helps to:
 - Compare different graph versions or datasets
 - Establish a baseline for other metrics
 
-{{ render_metric_table('instances') }}
+{{ metrics_table_single_general('instances') }}
 
 ## Queries
 
diff --git a/docs/metrics/general metrics/02_assertions.md b/docs/metrics/general metrics/02_assertions.md
index e671c34..11d911d 100644
--- a/docs/metrics/general metrics/02_assertions.md	
+++ b/docs/metrics/general metrics/02_assertions.md	
@@ -9,7 +9,7 @@ The metric helps to:
 - Track the growth of relationships over time
 - Compare connectivity between different graph versions
 
-{{ render_metric_table('assertions') }}
+{{ metrics_table_single_general('assertions') }}
 
 ## Queries
 
diff --git a/docs/metrics/general metrics/03_linkage_degree.md b/docs/metrics/general metrics/03_linkage_degree.md
index 86bdbc9..e640cf4 100644
--- a/docs/metrics/general metrics/03_linkage_degree.md	
+++ b/docs/metrics/general metrics/03_linkage_degree.md	
@@ -9,7 +9,7 @@ The metric provides insights into:
 - Identification of highly connected or isolated entities
 - Overall graph connectivity patterns
 
-{{ render_metric_table('linkage') }}
+{{ metrics_table_single_general('linkage') }}
 
 ## Queries
 
diff --git a/docs/metrics/general metrics/04_outgoing_edges.md b/docs/metrics/general metrics/04_outgoing_edges.md
index f27874e..2ecec14 100644
--- a/docs/metrics/general metrics/04_outgoing_edges.md	
+++ b/docs/metrics/general metrics/04_outgoing_edges.md	
@@ -2,7 +2,7 @@
 
 This metric determines the median number of outgoing edges across all nodes in the graph. The calculation requires multiple steps.
 
-{{ render_metric_table('edges_out') }}
+{{ metrics_table_single_general('edges_out') }}
 
 ## Queries
 
diff --git a/docs/metrics/general metrics/05_incoming_edges.md b/docs/metrics/general metrics/05_incoming_edges.md
index 1f9c99e..899f5ea 100644
--- a/docs/metrics/general metrics/05_incoming_edges.md	
+++ b/docs/metrics/general metrics/05_incoming_edges.md	
@@ -2,7 +2,7 @@
 
 This metric determines the median number of incoming edges across all nodes in the graph. The calculation requires multiple steps.
 
-{{ render_metric_table('edges_in') }}
+{{ metrics_table_single_general('edges_in') }}
 
 ## Queries
 
diff --git a/docs/metrics/index.md b/docs/metrics/index.md
index f3e7249..e946a39 100644
--- a/docs/metrics/index.md
+++ b/docs/metrics/index.md
@@ -11,13 +11,13 @@ These metrics analyze the entire knowledge graph structure and provide insights
 - Graph density and distribution
 - Edge statistics (incoming/outgoing)
 
-{{ general_metrics_table() }}
+{{ metrics_table_overview_general() }}
 
 ## Resource-specific Metrics
 
 These metrics focus on specific resource types within the knowledge graph:
 
-{{ resources_metrics_table() }}
+{{ metrics_table_overview_resource() }}
 
 ## About the Metrics
 
-- 
GitLab


From 71ad7bc8f004e47c7d72712afb32d5bcc9af4b39 Mon Sep 17 00:00:00 2001
From: Ralf Klammer <ralf.klammer@tu-dresden.de>
Date: Tue, 18 Mar 2025 13:30:51 +0100
Subject: [PATCH 31/59] [metrics] Remove numbers from filenames

---
 docs/metrics/fromality_metrics/classes.md     |  0
 .../{02_assertions.md => assertions.md}       |  0
 ...05_incoming_edges.md => edges_incoming.md} |  0
 ...04_outgoing_edges.md => edges_outgoing.md} |  0
 .../{01_instances.md => instances.md}         |  0
 ...03_linkage_degree.md => linkage_degree.md} |  0
 .../{16_aggregator.md => aggregator.md}       |  0
 .../{10_article_lhb.md => article_lhb.md}     |  0
 .../{15_dataservice.md => data_service.md}    |  0
 .../{06_dataset.md => dataset.md}             |  0
 ...rning_resource.md => learning_resource.md} |  0
 .../{12_organization.md => organization.md}   |  0
 .../{17_person.md => person.md}               |  0
 .../{07_publication.md => publication.md}     |  0
 .../{18_registry.md => registry.md}           |  0
 .../{09_repository.md => repository.md}       |  0
 .../{14_service.md => service.md}             |  0
 .../{13_software.md => software.md}           |  0
 .../{11_standards.md => standards.md}         |  0
 scripts/kg_analysis/interfaces/resources.py   | 26 +++++++++----------
 20 files changed, 13 insertions(+), 13 deletions(-)
 create mode 100644 docs/metrics/fromality_metrics/classes.md
 rename docs/metrics/general metrics/{02_assertions.md => assertions.md} (100%)
 rename docs/metrics/general metrics/{05_incoming_edges.md => edges_incoming.md} (100%)
 rename docs/metrics/general metrics/{04_outgoing_edges.md => edges_outgoing.md} (100%)
 rename docs/metrics/general metrics/{01_instances.md => instances.md} (100%)
 rename docs/metrics/general metrics/{03_linkage_degree.md => linkage_degree.md} (100%)
 rename docs/metrics/resource metrics/{16_aggregator.md => aggregator.md} (100%)
 rename docs/metrics/resource metrics/{10_article_lhb.md => article_lhb.md} (100%)
 rename docs/metrics/resource metrics/{15_dataservice.md => data_service.md} (100%)
 rename docs/metrics/resource metrics/{06_dataset.md => dataset.md} (100%)
 rename docs/metrics/resource metrics/{08_learning_resource.md => learning_resource.md} (100%)
 rename docs/metrics/resource metrics/{12_organization.md => organization.md} (100%)
 rename docs/metrics/resource metrics/{17_person.md => person.md} (100%)
 rename docs/metrics/resource metrics/{07_publication.md => publication.md} (100%)
 rename docs/metrics/resource metrics/{18_registry.md => registry.md} (100%)
 rename docs/metrics/resource metrics/{09_repository.md => repository.md} (100%)
 rename docs/metrics/resource metrics/{14_service.md => service.md} (100%)
 rename docs/metrics/resource metrics/{13_software.md => software.md} (100%)
 rename docs/metrics/resource metrics/{11_standards.md => standards.md} (100%)

diff --git a/docs/metrics/fromality_metrics/classes.md b/docs/metrics/fromality_metrics/classes.md
new file mode 100644
index 0000000..e69de29
diff --git a/docs/metrics/general metrics/02_assertions.md b/docs/metrics/general metrics/assertions.md
similarity index 100%
rename from docs/metrics/general metrics/02_assertions.md
rename to docs/metrics/general metrics/assertions.md
diff --git a/docs/metrics/general metrics/05_incoming_edges.md b/docs/metrics/general metrics/edges_incoming.md
similarity index 100%
rename from docs/metrics/general metrics/05_incoming_edges.md
rename to docs/metrics/general metrics/edges_incoming.md
diff --git a/docs/metrics/general metrics/04_outgoing_edges.md b/docs/metrics/general metrics/edges_outgoing.md
similarity index 100%
rename from docs/metrics/general metrics/04_outgoing_edges.md
rename to docs/metrics/general metrics/edges_outgoing.md
diff --git a/docs/metrics/general metrics/01_instances.md b/docs/metrics/general metrics/instances.md
similarity index 100%
rename from docs/metrics/general metrics/01_instances.md
rename to docs/metrics/general metrics/instances.md
diff --git a/docs/metrics/general metrics/03_linkage_degree.md b/docs/metrics/general metrics/linkage_degree.md
similarity index 100%
rename from docs/metrics/general metrics/03_linkage_degree.md
rename to docs/metrics/general metrics/linkage_degree.md
diff --git a/docs/metrics/resource metrics/16_aggregator.md b/docs/metrics/resource metrics/aggregator.md
similarity index 100%
rename from docs/metrics/resource metrics/16_aggregator.md
rename to docs/metrics/resource metrics/aggregator.md
diff --git a/docs/metrics/resource metrics/10_article_lhb.md b/docs/metrics/resource metrics/article_lhb.md
similarity index 100%
rename from docs/metrics/resource metrics/10_article_lhb.md
rename to docs/metrics/resource metrics/article_lhb.md
diff --git a/docs/metrics/resource metrics/15_dataservice.md b/docs/metrics/resource metrics/data_service.md
similarity index 100%
rename from docs/metrics/resource metrics/15_dataservice.md
rename to docs/metrics/resource metrics/data_service.md
diff --git a/docs/metrics/resource metrics/06_dataset.md b/docs/metrics/resource metrics/dataset.md
similarity index 100%
rename from docs/metrics/resource metrics/06_dataset.md
rename to docs/metrics/resource metrics/dataset.md
diff --git a/docs/metrics/resource metrics/08_learning_resource.md b/docs/metrics/resource metrics/learning_resource.md
similarity index 100%
rename from docs/metrics/resource metrics/08_learning_resource.md
rename to docs/metrics/resource metrics/learning_resource.md
diff --git a/docs/metrics/resource metrics/12_organization.md b/docs/metrics/resource metrics/organization.md
similarity index 100%
rename from docs/metrics/resource metrics/12_organization.md
rename to docs/metrics/resource metrics/organization.md
diff --git a/docs/metrics/resource metrics/17_person.md b/docs/metrics/resource metrics/person.md
similarity index 100%
rename from docs/metrics/resource metrics/17_person.md
rename to docs/metrics/resource metrics/person.md
diff --git a/docs/metrics/resource metrics/07_publication.md b/docs/metrics/resource metrics/publication.md
similarity index 100%
rename from docs/metrics/resource metrics/07_publication.md
rename to docs/metrics/resource metrics/publication.md
diff --git a/docs/metrics/resource metrics/18_registry.md b/docs/metrics/resource metrics/registry.md
similarity index 100%
rename from docs/metrics/resource metrics/18_registry.md
rename to docs/metrics/resource metrics/registry.md
diff --git a/docs/metrics/resource metrics/09_repository.md b/docs/metrics/resource metrics/repository.md
similarity index 100%
rename from docs/metrics/resource metrics/09_repository.md
rename to docs/metrics/resource metrics/repository.md
diff --git a/docs/metrics/resource metrics/14_service.md b/docs/metrics/resource metrics/service.md
similarity index 100%
rename from docs/metrics/resource metrics/14_service.md
rename to docs/metrics/resource metrics/service.md
diff --git a/docs/metrics/resource metrics/13_software.md b/docs/metrics/resource metrics/software.md
similarity index 100%
rename from docs/metrics/resource metrics/13_software.md
rename to docs/metrics/resource metrics/software.md
diff --git a/docs/metrics/resource metrics/11_standards.md b/docs/metrics/resource metrics/standards.md
similarity index 100%
rename from docs/metrics/resource metrics/11_standards.md
rename to docs/metrics/resource metrics/standards.md
diff --git a/scripts/kg_analysis/interfaces/resources.py b/scripts/kg_analysis/interfaces/resources.py
index 203685c..302ad9c 100644
--- a/scripts/kg_analysis/interfaces/resources.py
+++ b/scripts/kg_analysis/interfaces/resources.py
@@ -86,55 +86,55 @@ query_templates = {
 
 resource_types = {
     "dataset": {
-        "file": "06_dataset.md",
+        "file": "dataset.md",
         "uri": "<http://www.w3.org/ns/dcat#Dataset>",
     },
     "publication": {
-        "file": "07_publication.md",
+        "file": "publication.md",
         "uri": "<http://nfdi4earth.de/ontology/Registry>",
     },
     "learning_resource": {
-        "file": "08_learning_resource.md",
+        "file": "learning_resource.md",
         "uri": "<http://schema.org/LearningResource>",
     },
     "repository": {
-        "file": "09_repository.md",
+        "file": "repository.md",
         "uri": "<http://nfdi4earth.de/ontology/Repository>",
     },
     "article_lhb": {
-        "file": "10_article_lhb.md",
+        "file": "article_lhb.md",
         "uri": "<http://nfdi4earth.de/ontology/LHBArticle>",
     },
     "standards": {
-        "file": "11_standards.md",
+        "file": "standards.md",
         "uri": "<http://nfdi4earth.de/ontology/MetadataStandard>",
     },
     # "organization": {
-    #     "file": "12_organization.md",
+    #     "file": "organization.md",
     #     "uri": "<http://xmlns.com/foaf/0.1/Organization>",
     # },
     "software": {
-        "file": "13_software.md",
+        "file": "software.md",
         "uri": "<http://schema.org/SoftwareSourceCode>",
     },
     "service": {
-        "file": "14_service.md",
+        "file": "service.md",
         "uri": "<http://www.w3.org/ns/sparql-service-description#Service>",
     },
     "data_service": {
-        "file": "15_data_service.md",
+        "file": "data_service.md",
         "uri": "<http://www.w3.org/ns/dcat#DataService>",
     },
     "aggregator": {
-        "file": "16_aggregator.md",
+        "file": "aggregator.md",
         "uri": "<http://nfdi4earth.de/ontology/Aggregator>",
     },
     "person": {
-        "file": "17_person.md",
+        "file": "person.md",
         "uri": "<http://schema.org/Person>",
     },
     "registry": {
-        "file": "18_registry.md",
+        "file": "registry.md",
         "uri": "<http://nfdi4earth.de/ontology/Registry>",
     },
 }
-- 
GitLab


From 0ea113ca78695195478cc8f25ae42344e520d94a Mon Sep 17 00:00:00 2001
From: Ralf Klammer <ralf.klammer@tu-dresden.de>
Date: Tue, 18 Mar 2025 16:47:34 +0100
Subject: [PATCH 32/59] [metrics] Adding 'complexity metrics'

---
 docs/metrics/fromality_metrics/classes.md     |  0
 .../{instances.md => 01_instances.md}         |  0
 .../{assertions.md => 02_assertions.md}       |  0
 ...linkage_degree.md => 03_linkage_degree.md} |  0
 ...edges_incoming.md => 04_edges_incoming.md} |  0
 ...edges_outgoing.md => 05_edges_outgoing.md} |  0
 docs/metrics/index.md                         |  2 +
 .../schema_complexity_metrics/01_overview.md  | 60 +++++++++++++++++++
 .../schema_complexity_metrics/02_classes.md   | 31 ++++++++++
 .../03_properties.md                          | 51 ++++++++++++++++
 .../schema_complexity_metrics/04_depth.md     | 35 +++++++++++
 .../schema_complexity_metrics/05_width.md     | 39 ++++++++++++
 .../06_restrictions.md                        | 40 +++++++++++++
 .../schema_complexity_metrics/07_axioms.md    | 39 ++++++++++++
 queries/metrics/RF_001.rq                     | 23 +++++++
 queries/metrics/RF_002_1.rq                   | 25 ++++++++
 queries/metrics/RF_002_2.rq                   | 14 +++++
 queries/metrics/RF_002_3.rq                   | 14 +++++
 queries/metrics/RF_002_4.rq                   | 29 +++++++++
 queries/metrics/RF_002_5.rq                   | 29 +++++++++
 queries/metrics/RF_002_6.rq                   | 45 ++++++++++++++
 queries/metrics/RF_003.rq                     | 33 ++++++++++
 queries/metrics/RF_004.rq                     | 43 +++++++++++++
 queries/metrics/RF_005.rq                     | 38 ++++++++++++
 queries/metrics/RF_006.rq                     | 37 ++++++++++++
 queries/metrics/RF_007.rq                     | 21 +++++++
 26 files changed, 648 insertions(+)
 delete mode 100644 docs/metrics/fromality_metrics/classes.md
 rename docs/metrics/general metrics/{instances.md => 01_instances.md} (100%)
 rename docs/metrics/general metrics/{assertions.md => 02_assertions.md} (100%)
 rename docs/metrics/general metrics/{linkage_degree.md => 03_linkage_degree.md} (100%)
 rename docs/metrics/general metrics/{edges_incoming.md => 04_edges_incoming.md} (100%)
 rename docs/metrics/general metrics/{edges_outgoing.md => 05_edges_outgoing.md} (100%)
 create mode 100644 docs/metrics/schema_complexity_metrics/01_overview.md
 create mode 100644 docs/metrics/schema_complexity_metrics/02_classes.md
 create mode 100644 docs/metrics/schema_complexity_metrics/03_properties.md
 create mode 100644 docs/metrics/schema_complexity_metrics/04_depth.md
 create mode 100644 docs/metrics/schema_complexity_metrics/05_width.md
 create mode 100644 docs/metrics/schema_complexity_metrics/06_restrictions.md
 create mode 100644 docs/metrics/schema_complexity_metrics/07_axioms.md
 create mode 100644 queries/metrics/RF_001.rq
 create mode 100644 queries/metrics/RF_002_1.rq
 create mode 100644 queries/metrics/RF_002_2.rq
 create mode 100644 queries/metrics/RF_002_3.rq
 create mode 100644 queries/metrics/RF_002_4.rq
 create mode 100644 queries/metrics/RF_002_5.rq
 create mode 100644 queries/metrics/RF_002_6.rq
 create mode 100644 queries/metrics/RF_003.rq
 create mode 100644 queries/metrics/RF_004.rq
 create mode 100644 queries/metrics/RF_005.rq
 create mode 100644 queries/metrics/RF_006.rq
 create mode 100644 queries/metrics/RF_007.rq

diff --git a/docs/metrics/fromality_metrics/classes.md b/docs/metrics/fromality_metrics/classes.md
deleted file mode 100644
index e69de29..0000000
diff --git a/docs/metrics/general metrics/instances.md b/docs/metrics/general metrics/01_instances.md
similarity index 100%
rename from docs/metrics/general metrics/instances.md
rename to docs/metrics/general metrics/01_instances.md
diff --git a/docs/metrics/general metrics/assertions.md b/docs/metrics/general metrics/02_assertions.md
similarity index 100%
rename from docs/metrics/general metrics/assertions.md
rename to docs/metrics/general metrics/02_assertions.md
diff --git a/docs/metrics/general metrics/linkage_degree.md b/docs/metrics/general metrics/03_linkage_degree.md
similarity index 100%
rename from docs/metrics/general metrics/linkage_degree.md
rename to docs/metrics/general metrics/03_linkage_degree.md
diff --git a/docs/metrics/general metrics/edges_incoming.md b/docs/metrics/general metrics/04_edges_incoming.md
similarity index 100%
rename from docs/metrics/general metrics/edges_incoming.md
rename to docs/metrics/general metrics/04_edges_incoming.md
diff --git a/docs/metrics/general metrics/edges_outgoing.md b/docs/metrics/general metrics/05_edges_outgoing.md
similarity index 100%
rename from docs/metrics/general metrics/edges_outgoing.md
rename to docs/metrics/general metrics/05_edges_outgoing.md
diff --git a/docs/metrics/index.md b/docs/metrics/index.md
index e946a39..801ddcc 100644
--- a/docs/metrics/index.md
+++ b/docs/metrics/index.md
@@ -19,6 +19,8 @@ These metrics focus on specific resource types within the knowledge graph:
 
 {{ metrics_table_overview_resource() }}
 
+{{ include_if_exists("docs/metrics/schema_complexity_metrics/01_overview.md") }}
+
 ## About the Metrics
 
 All metrics:
diff --git a/docs/metrics/schema_complexity_metrics/01_overview.md b/docs/metrics/schema_complexity_metrics/01_overview.md
new file mode 100644
index 0000000..9bb98a6
--- /dev/null
+++ b/docs/metrics/schema_complexity_metrics/01_overview.md
@@ -0,0 +1,60 @@
+# Overview - Schema Complexity Metrics
+
+To calculate the complexity of RDF schemas, we combine the presented formality metrics focusing on both structural and semantic aspects.
+
+## 1. Basic Structural Complexity
+
+These metrics measure the size and diversity of the RDF schema:
+
+- **[Number of classes](classes.md)** (_C_) → More classes indicate a more complex schema.
+- **[Number of properties](properties.md)** (_P_) → More properties mean more relationships between entities.
+- **[Average class hierarchy depth](depth.md)** (_D_avg_) → The mean number of hierarchy levels in the schema.
+- **[Average class hierarchy width](width.md)** (_W_avg_) → The mean number of sibling classes at each hierarchy level.
+
+A structural complexity score can be calculated as:
+
+    C_structural = w1 * C + w2 * P + w3 * D_avg + w4 * W_avg
+
+where _w_i_ are weights that determine the relative importance of each factor.
+
+## 2. Semantic Complexity
+
+If OWL is used, the complexity increases due to advanced semantics:
+
+- **[Number of restrictions](restrictions.md)** (_R_) → More restrictions indicate more constraints and rules.
+- **[Number of logical axioms](axioms.md)** (_A_) → More axioms mean more logical statements and inferences.
+
+A semantic complexity score can be calculated as:
+
+    C_semantic = w5 * R + w6 * A
+
+where _w_i_ are weights that determine the relative importance of each factor.
+
+## 3. Combined Formula for Schema Complexity
+
+To create an overall complexity score, we can combine both structural and semantic aspects:
+
+    C_schema = w1 * C + w2 * P + w3 * D_avg + w4 * W_avg + w5 * R + w6 * A
+
+where _w_i_ are weights that determine the relative importance of each factor.
+
+This formula provides a single numerical value representing schema complexity, which can be normalized (e.g., 0–100) for comparison across different RDF schemas.
+
+## References
+
+> !!!Not the final references!!!
+
+1. Structural Metrics for Ontologies
+   - Gómez-Pérez et al. (2004) – "Evaluation of Ontologies"
+   - Describes metrics like number of classes, hierarchy depth, and relations
+   - Source: https://link.springer.com/chapter/10.1007/978-3-540-30202-5_3
+
+2. OWL and Schema Complexity Measurements
+   - Tartir & Arpinar (2010) – "Ontology Evaluation and Ranking using OntoQA"
+   - Develops the OntoQA model combining structural and semantic metrics
+   - Source: https://doi.org/10.1109/ICDEW.2005.43
+
+3. SPARQL Analysis and RDF Complexity
+   - Lanzenberger et al. (2008) – "Ontology Evaluation – State of the Art"
+   - Describes hierarchical depth as key metric for RDF schema complexity
+   - Source: https://doi.org/10.1007/978-3-540-92673-3_10
\ No newline at end of file
diff --git a/docs/metrics/schema_complexity_metrics/02_classes.md b/docs/metrics/schema_complexity_metrics/02_classes.md
new file mode 100644
index 0000000..2b2da15
--- /dev/null
+++ b/docs/metrics/schema_complexity_metrics/02_classes.md
@@ -0,0 +1,31 @@
+# Number of Schema Classes
+
+This metric counts the total number of classes defined in our schema.
+
+## SPARQL Query
+
+```sparql
+{{ include_if_exists("queries/metrics/RF_001.rq") }}
+```
+
+## Description
+
+This query:
+
+- Counts distinct classes defined using rdfs:Class, owl:Class, or sh:NodeShape
+- Excludes built-in classes from RDF, RDFS
+- Returns a single number representing the total count of user-defined classes in the schema
+
+## Interpretation
+
+A higher number indicates:
+
+- More complex domain modeling
+- Broader coverage of concepts
+- More detailed classification system
+
+A lower number might suggest:
+
+- Simpler schema structure
+- Focus on core concepts
+- Potential for extended modeling
diff --git a/docs/metrics/schema_complexity_metrics/03_properties.md b/docs/metrics/schema_complexity_metrics/03_properties.md
new file mode 100644
index 0000000..6547ab8
--- /dev/null
+++ b/docs/metrics/schema_complexity_metrics/03_properties.md
@@ -0,0 +1,51 @@
+# Number of Schema Properties
+
+This metric analyzes the properties defined in our schema through multiple aspects.
+
+## Total Properties Count
+
+```sparql
+{{ include_if_exists("queries/metrics/RF_002_1.rq") }}
+```
+
+## Object Properties Count
+
+```sparql
+{{ include_if_exists("queries/metrics/RF_002_2.rq") }}
+```
+
+## Datatype Properties Count
+
+```sparql
+{{ include_if_exists("queries/metrics/RF_002_3.rq") }}
+```
+
+## Properties with Domain
+
+```sparql
+{{ include_if_exists("queries/metrics/RF_002_4.rq") }}
+```
+
+## Properties with Range
+
+```sparql
+{{ include_if_exists("queries/metrics/RF_002_5.rq") }}
+```
+
+## Combined Metrics Query
+
+```sparql
+{{ include_if_exists("queries/metrics/RF_002_6.rq") }}
+```
+
+## Interpretation
+
+Each metric provides specific insights:
+
+1. **Total** Properties: Overall schema complexity
+2. **Object** Properties: Resource interlinking capability
+3. **Datatype** Properties: Attribute richness
+4. **Domain** Coverage: Property context definition
+5. **Range** Coverage: Value constraints and type safety
+
+These separate metrics allow for more detailed analysis and easier maintenance. Additionally, the **combined query** provides a comprehensive overview in a single execution.
diff --git a/docs/metrics/schema_complexity_metrics/04_depth.md b/docs/metrics/schema_complexity_metrics/04_depth.md
new file mode 100644
index 0000000..2dc4a40
--- /dev/null
+++ b/docs/metrics/schema_complexity_metrics/04_depth.md
@@ -0,0 +1,35 @@
+# Depth of Schema (Average)
+
+This metric calculates the average depth of the class hierarchy in the schema.
+
+## SPARQL Query
+
+```sparql
+{{ include_if_exists("queries/metrics/RF_003.rq") }}
+```
+
+## Description
+
+This query:
+- Identifies all user-defined classes
+- Calculates the path length to all superclasses
+- Determines the average of these path lengths
+- Excludes standard RDF/OWL classes
+
+## Interpretation
+
+A higher average value indicates:
+- Deeper class hierarchies
+- More detailed concept modeling
+- Stronger specialization
+
+A lower value suggests:
+- Flatter hierarchies
+- Broader rather than deeper structuring
+- Potentially easier maintenance
+
+## Notes
+
+- Depth is calculated by counting superclass relationships
+- owl:Thing is not counted
+- Multiple inheritance is considered
\ No newline at end of file
diff --git a/docs/metrics/schema_complexity_metrics/05_width.md b/docs/metrics/schema_complexity_metrics/05_width.md
new file mode 100644
index 0000000..3f0c38e
--- /dev/null
+++ b/docs/metrics/schema_complexity_metrics/05_width.md
@@ -0,0 +1,39 @@
+# Width of Schema (Average)
+
+This metric calculates the average number of subclasses per class (branching factor) in the schema.
+
+## SPARQL Query
+
+```sparql
+{{ include_if_exists("queries/metrics/RF_004.rq") }}
+```
+
+## Description
+
+This query:
+
+- Identifies all classes in the schema
+- Counts the number of direct subclasses for each class
+- Calculates the average number of subclasses (branching factor)
+- Excludes built-in RDF/OWL classes
+- Considers only direct subclass relationships (no transitive closure)
+
+## Interpretation
+
+A higher average width indicates:
+
+- Broader classification at each level
+- More horizontal spread in the taxonomy
+- Potentially flatter hierarchies
+
+A lower average width suggests:
+
+- More vertical organization
+- More specialized hierarchies
+- Potentially deeper class trees
+
+## Notes
+
+- Only counts direct subclass relationships
+- Includes classes with no subclasses (count = 0)
+- Multiple inheritance is handled correctly through DISTINCT counting
\ No newline at end of file
diff --git a/docs/metrics/schema_complexity_metrics/06_restrictions.md b/docs/metrics/schema_complexity_metrics/06_restrictions.md
new file mode 100644
index 0000000..4f27219
--- /dev/null
+++ b/docs/metrics/schema_complexity_metrics/06_restrictions.md
@@ -0,0 +1,40 @@
+# Number of Restrictions
+
+This metric counts the various types of OWL restrictions defined in the schema.
+
+## SPARQL Query
+
+```sparql
+{{ include_if_exists("queries/metrics/RF_006.rq") }}
+```
+
+## Description
+
+This query counts:
+
+- Total number of OWL restrictions
+- Specific restriction types:
+  - someValuesFrom restrictions
+  - allValuesFrom restrictions
+  - hasValue restrictions
+  - Cardinality restrictions (min, max, exact)
+
+## Interpretation
+
+Higher numbers indicate:
+
+- More constrained schema
+- More precise data modeling
+- Higher validation requirements
+
+Breakdown by type shows:
+
+- Value constraints (someValues/allValues)
+- Fixed value requirements (hasValue)
+- Quantity rules (cardinality)
+
+## Notes
+
+- Excludes built-in OWL restrictions
+- One restriction can have multiple types
+- Cardinality includes min/max/exact
\ No newline at end of file
diff --git a/docs/metrics/schema_complexity_metrics/07_axioms.md b/docs/metrics/schema_complexity_metrics/07_axioms.md
new file mode 100644
index 0000000..16434c5
--- /dev/null
+++ b/docs/metrics/schema_complexity_metrics/07_axioms.md
@@ -0,0 +1,39 @@
+# Number of Logical Axioms
+
+This metric counts the various types of OWL logical axioms defined in the schema.
+
+## SPARQL Query
+
+```sparql
+{{ include_if_exists("queries/metrics/RF_007.rq") }}
+```
+
+## Description
+
+This query counts OWL logical axioms including:
+
+- Equivalent classes (owl:equivalentClass)
+- Disjoint classes (owl:disjointWith)
+- Complement classes (owl:complementOf)
+- Intersection classes (owl:intersectionOf)
+- Union classes (owl:unionOf)
+
+## Interpretation
+
+Higher numbers indicate:
+
+- More complex logical relationships
+- Richer semantic modeling
+- Greater inferencing potential
+
+Type distribution shows:
+
+- Class equivalence relationships
+- Class disjointness constraints
+- Complex class definitions
+
+## Notes
+
+- Excludes built-in OWL axioms
+- Counts distinct class usages
+- Combined total gives overall axiom complexity
\ No newline at end of file
diff --git a/queries/metrics/RF_001.rq b/queries/metrics/RF_001.rq
new file mode 100644
index 0000000..9ccb05d
--- /dev/null
+++ b/queries/metrics/RF_001.rq
@@ -0,0 +1,23 @@
+# This query counts the number of distinct RDFS and OWL classes,
+# excluding built-in classes from RDF/RDFS/OWL vocabularies
+
+PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
+PREFIX owl: <http://www.w3.org/2002/07/owl#>
+
+SELECT (COUNT(DISTINCT ?class) AS ?numberOfClasses)
+WHERE {
+    {
+        # Count RDFS classes
+        ?class a rdfs:Class .
+    }
+    UNION
+    {
+        # Count OWL classes
+        ?class a owl:Class .
+    }
+
+    # Filter out built-in classes from RDF/RDFS/OWL vocabularies
+    FILTER(!STRSTARTS(STR(?class), "http://www.w3.org/2002/07/owl"))
+    FILTER(!STRSTARTS(STR(?class), "http://www.w3.org/2000/01/rdf-schema"))
+    FILTER(!STRSTARTS(STR(?class), "http://www.w3.org/1999/02/22-rdf-syntax-ns"))
+}
diff --git a/queries/metrics/RF_002_1.rq b/queries/metrics/RF_002_1.rq
new file mode 100644
index 0000000..423b129
--- /dev/null
+++ b/queries/metrics/RF_002_1.rq
@@ -0,0 +1,25 @@
+# This SPARQL query counts the total number of distinct properties,
+# excluding built-in properties
+
+PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>
+PREFIX owl: <http://www.w3.org/2002/07/owl#>
+
+SELECT (COUNT(DISTINCT ?property) AS ?totalProperties)
+WHERE {
+  {
+    ?property a rdf:Property .
+  }
+  UNION
+  {
+    ?property a owl:ObjectProperty .
+  }
+  UNION
+  {
+    ?property a owl:DatatypeProperty .
+  }
+
+  # Filter for built-in properties
+  FILTER(!STRSTARTS(STR(?property), "http://www.w3.org/2002/07/owl"))
+  FILTER(!STRSTARTS(STR(?property), "http://www.w3.org/2000/01/rdf-schema"))
+  FILTER(!STRSTARTS(STR(?property), "http://www.w3.org/1999/02/22-rdf-syntax-ns"))
+}
\ No newline at end of file
diff --git a/queries/metrics/RF_002_2.rq b/queries/metrics/RF_002_2.rq
new file mode 100644
index 0000000..4338eb7
--- /dev/null
+++ b/queries/metrics/RF_002_2.rq
@@ -0,0 +1,14 @@
+# This SPARQL query counts the number of distinct object properties,
+# excluding built-in properties
+
+PREFIX owl: <http://www.w3.org/2002/07/owl#>
+
+SELECT (COUNT(DISTINCT ?property) AS ?objectProperties)
+WHERE {
+  ?property a owl:ObjectProperty .
+
+  # Filter für eingebaute Properties
+  FILTER(!STRSTARTS(STR(?property), "http://www.w3.org/2002/07/owl"))
+  FILTER(!STRSTARTS(STR(?property), "http://www.w3.org/2000/01/rdf-schema"))
+  FILTER(!STRSTARTS(STR(?property), "http://www.w3.org/1999/02/22-rdf-syntax-ns"))
+}
\ No newline at end of file
diff --git a/queries/metrics/RF_002_3.rq b/queries/metrics/RF_002_3.rq
new file mode 100644
index 0000000..1fa9bcd
--- /dev/null
+++ b/queries/metrics/RF_002_3.rq
@@ -0,0 +1,14 @@
+# This SPARQL query counts the number of distinct datatype properties,
+# excluding built-in properties
+
+PREFIX owl: <http://www.w3.org/2002/07/owl#>
+
+SELECT (COUNT(DISTINCT ?property) AS ?datatypeProperties)
+WHERE {
+  ?property a owl:DatatypeProperty .
+
+  # Filter out built-in properties
+  FILTER(!STRSTARTS(STR(?property), "http://www.w3.org/2002/07/owl"))
+  FILTER(!STRSTARTS(STR(?property), "http://www.w3.org/2000/01/rdf-schema"))
+  FILTER(!STRSTARTS(STR(?property), "http://www.w3.org/1999/02/22-rdf-syntax-ns"))
+}
\ No newline at end of file
diff --git a/queries/metrics/RF_002_4.rq b/queries/metrics/RF_002_4.rq
new file mode 100644
index 0000000..82ebe44
--- /dev/null
+++ b/queries/metrics/RF_002_4.rq
@@ -0,0 +1,29 @@
+# This SPARQL query counts the number of distinct properties with a
+# specified domain, excluding built-in properties
+
+PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
+PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>
+PREFIX owl: <http://www.w3.org/2002/07/owl#>
+
+SELECT (COUNT(DISTINCT ?property) AS ?propertiesWithDomain)
+WHERE {
+  {
+    ?property a rdf:Property ;
+             rdfs:domain ?domain .
+  }
+  UNION
+  {
+    ?property a owl:ObjectProperty ;
+             rdfs:domain ?domain .
+  }
+  UNION
+  {
+    ?property a owl:DatatypeProperty ;
+             rdfs:domain ?domain .
+  }
+
+  # Filter out built-in properties
+  FILTER(!STRSTARTS(STR(?property), "http://www.w3.org/2002/07/owl"))
+  FILTER(!STRSTARTS(STR(?property), "http://www.w3.org/2000/01/rdf-schema"))
+  FILTER(!STRSTARTS(STR(?property), "http://www.w3.org/1999/02/22-rdf-syntax-ns"))
+}
diff --git a/queries/metrics/RF_002_5.rq b/queries/metrics/RF_002_5.rq
new file mode 100644
index 0000000..4fea73c
--- /dev/null
+++ b/queries/metrics/RF_002_5.rq
@@ -0,0 +1,29 @@
+# This query counts the number of distinct properties with a specified range,
+# excluding built-in properties
+
+PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
+PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>
+PREFIX owl: <http://www.w3.org/2002/07/owl#>
+
+SELECT (COUNT(DISTINCT ?property) AS ?propertiesWithRange)
+WHERE {
+  {
+    ?property a rdf:Property ;
+             rdfs:range ?range .
+  }
+  UNION
+  {
+    ?property a owl:ObjectProperty ;
+             rdfs:range ?range .
+  }
+  UNION
+  {
+    ?property a owl:DatatypeProperty ;
+             rdfs:range ?range .
+  }
+
+  # Filter out built-in properties
+  FILTER(!STRSTARTS(STR(?property), "http://www.w3.org/2002/07/owl"))
+  FILTER(!STRSTARTS(STR(?property), "http://www.w3.org/2000/01/rdf-schema"))
+  FILTER(!STRSTARTS(STR(?property), "http://www.w3.org/1999/02/22-rdf-syntax-ns"))
+}
diff --git a/queries/metrics/RF_002_6.rq b/queries/metrics/RF_002_6.rq
new file mode 100644
index 0000000..a4bfec1
--- /dev/null
+++ b/queries/metrics/RF_002_6.rq
@@ -0,0 +1,45 @@
+# This SPARQL query counts various types of properties in an RDF dataset,
+# excluding built-in properties.
+
+PREFIX owl: <http://www.w3.org/2002/07/owl#>
+PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>
+
+SELECT
+  (COUNT(DISTINCT ?property) AS ?totalProperties)
+  (COUNT(DISTINCT ?objectProperty) AS ?objectProperties)
+  (COUNT(DISTINCT ?datatypeProperty) AS ?datatypeProperties)
+  (COUNT(DISTINCT ?withDomain) AS ?propertiesWithDomain)
+  (COUNT(DISTINCT ?withRange) AS ?propertiesWithRange)
+WHERE {
+  {
+    # Count RDF/RDFS properties
+    ?property a rdf:Property .
+  }
+  UNION
+  {
+    # Count OWL Object Properties
+    ?property a owl:ObjectProperty .
+    BIND(?property AS ?objectProperty)
+  }
+  UNION
+  {
+    # Count OWL Datatype Properties
+    ?property a owl:DatatypeProperty .
+    BIND(?property AS ?datatypeProperty)
+  }
+
+  # Filter out built-in properties
+  FILTER(!STRSTARTS(STR(?property), "http://www.w3.org/2002/07/owl"))
+  FILTER(!STRSTARTS(STR(?property), "http://www.w3.org/2000/01/rdf-schema"))
+  FILTER(!STRSTARTS(STR(?property), "http://www.w3.org/1999/02/22-rdf-syntax-ns"))
+
+  # Check for domain and range definitions
+  OPTIONAL {
+    ?property rdfs:domain ?domain .
+    BIND(?property AS ?withDomain)
+  }
+  OPTIONAL {
+    ?property rdfs:range ?range .
+    BIND(?property AS ?withRange)
+  }
+}
\ No newline at end of file
diff --git a/queries/metrics/RF_003.rq b/queries/metrics/RF_003.rq
new file mode 100644
index 0000000..601397c
--- /dev/null
+++ b/queries/metrics/RF_003.rq
@@ -0,0 +1,33 @@
+# This SPARQL query calculates the average hierarchy depth of all classes
+# in an RDF dataset, excluding built-in RDF and OWL classes.
+
+PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
+PREFIX owl: <http://www.w3.org/2002/07/owl#>
+
+SELECT (AVG(?depth) AS ?averageHierarchyDepth)
+WHERE {
+  {
+    SELECT ?class (COUNT(?superClass) AS ?depth)
+    WHERE {
+      # Find all classes
+      {
+        ?class a rdfs:Class .
+      } UNION {
+        ?class a owl:Class .
+      }
+
+      # Ensure class exists
+      ?class rdfs:subClassOf ?directSuper .
+
+      # Calculate path to all superclasses
+      ?directSuper rdfs:subClassOf ?superClass .
+
+      # Filter out built-in classes
+      FILTER(!STRSTARTS(STR(?class), "http://www.w3.org/2002/07/owl"))
+      FILTER(!STRSTARTS(STR(?class), "http://www.w3.org/2000/01/rdf-schema"))
+      FILTER(!STRSTARTS(STR(?superClass), "http://www.w3.org/2002/07/owl"))
+      FILTER(!STRSTARTS(STR(?superClass), "http://www.w3.org/2000/01/rdf-schema"))
+    }
+    GROUP BY ?class
+  }
+}
\ No newline at end of file
diff --git a/queries/metrics/RF_004.rq b/queries/metrics/RF_004.rq
new file mode 100644
index 0000000..51ad8ed
--- /dev/null
+++ b/queries/metrics/RF_004.rq
@@ -0,0 +1,43 @@
+# This SPARQL query calculates the average branching factor of classes in
+# an RDF dataset.
+# The branching factor is defined as the average number of direct
+# subclasses per class.
+# The query first identifies all classes (both rdfs:Class and owl:Class)
+# and counts their direct subclasses, excluding built-in properties
+
+PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
+PREFIX owl: <http://www.w3.org/2002/07/owl#>
+
+SELECT (AVG(?subClassCount) AS ?averageBranchingFactor)
+WHERE {
+  {
+    SELECT ?class (COUNT(DISTINCT ?subClass) AS ?subClassCount)
+    WHERE {
+      # Find all classes that have subclasses
+      {
+        ?class a rdfs:Class .
+      } UNION {
+        ?class a owl:Class .
+      }
+
+      # Count direct subclasses
+      OPTIONAL {
+        ?subClass rdfs:subClassOf ?class .
+
+        # Ensure subClass is actually a class
+        {
+          ?subClass a rdfs:Class .
+        } UNION {
+          ?subClass a owl:Class .
+        }
+      }
+
+      # Filter out built-in classes
+      FILTER(!STRSTARTS(STR(?class), "http://www.w3.org/2002/07/owl"))
+      FILTER(!STRSTARTS(STR(?class), "http://www.w3.org/2000/01/rdf-schema"))
+      FILTER(!STRSTARTS(STR(?subClass), "http://www.w3.org/2002/07/owl"))
+      FILTER(!STRSTARTS(STR(?subClass), "http://www.w3.org/2000/01/rdf-schema"))
+    }
+    GROUP BY ?class
+  }
+}
\ No newline at end of file
diff --git a/queries/metrics/RF_005.rq b/queries/metrics/RF_005.rq
new file mode 100644
index 0000000..003e342
--- /dev/null
+++ b/queries/metrics/RF_005.rq
@@ -0,0 +1,38 @@
+PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>
+PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
+PREFIX owl: <http://www.w3.org/2002/07/owl#>
+
+SELECT
+  (COUNT(DISTINCT ?class) AS ?totalClasses)
+  (COUNT(DISTINCT ?property) AS ?totalProperties)
+  (COUNT(DISTINCT ?restriction) AS ?totalRestrictions)
+  (?totalClasses + ?totalProperties + ?totalRestrictions AS ?complexityScore)
+WHERE {
+  {
+    # Count Classes
+    {
+      ?class a rdfs:Class .
+    } UNION {
+      ?class a owl:Class .
+    }
+    FILTER(!STRSTARTS(STR(?class), "http://www.w3.org/"))
+  }
+  UNION
+  {
+    # Count Properties
+    {
+      ?property a rdf:Property .
+    } UNION {
+      ?property a owl:ObjectProperty .
+    } UNION {
+      ?property a owl:DatatypeProperty .
+    }
+    FILTER(!STRSTARTS(STR(?property), "http://www.w3.org/"))
+  }
+  UNION
+  {
+    # Count Restrictions
+    ?restriction a owl:Restriction .
+    FILTER(!STRSTARTS(STR(?restriction), "http://www.w3.org/"))
+  }
+}
\ No newline at end of file
diff --git a/queries/metrics/RF_006.rq b/queries/metrics/RF_006.rq
new file mode 100644
index 0000000..6a02413
--- /dev/null
+++ b/queries/metrics/RF_006.rq
@@ -0,0 +1,37 @@
+PREFIX owl: <http://www.w3.org/2002/07/owl#>
+PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
+
+SELECT
+  (COUNT(DISTINCT ?restriction) as ?totalRestrictions)
+  (COUNT(DISTINCT ?someValues) as ?someValuesFrom)
+  (COUNT(DISTINCT ?allValues) as ?allValuesFrom)
+  (COUNT(DISTINCT ?hasValue) as ?hasValueRestrictions)
+  (COUNT(DISTINCT ?cardinality) as ?cardinalityRestrictions)
+WHERE {
+  {
+    ?restriction a owl:Restriction .
+    FILTER(!STRSTARTS(STR(?restriction), "http://www.w3.org/"))
+  }
+  OPTIONAL {
+    ?someValues a owl:Restriction ;
+                owl:someValuesFrom ?target1 .
+  }
+  OPTIONAL {
+    ?allValues a owl:Restriction ;
+               owl:allValuesFrom ?target2 .
+  }
+  OPTIONAL {
+    ?hasValue a owl:Restriction ;
+              owl:hasValue ?target3 .
+  }
+  OPTIONAL {
+    ?cardinality a owl:Restriction .
+    {
+      ?cardinality owl:cardinality ?card1 .
+    } UNION {
+      ?cardinality owl:minCardinality ?card2 .
+    } UNION {
+      ?cardinality owl:maxCardinality ?card3 .
+    }
+  }
+}
\ No newline at end of file
diff --git a/queries/metrics/RF_007.rq b/queries/metrics/RF_007.rq
new file mode 100644
index 0000000..2ba5613
--- /dev/null
+++ b/queries/metrics/RF_007.rq
@@ -0,0 +1,21 @@
+# This query counts the total number of distinct logical axioms in the dataset,
+# excluding built-in properties.
+
+PREFIX owl: <http://www.w3.org/2002/07/owl#>
+
+SELECT
+    (COUNT(DISTINCT ?axiom) as ?totalLogicalAxioms)
+WHERE {
+    VALUES ?property {
+        owl:equivalentClass
+        owl:disjointWith
+        owl:complementOf
+        owl:intersectionOf
+        owl:unionOf
+    }
+
+    ?axiom ?property ?target .
+
+    # Filter for built-in properties
+    FILTER(!STRSTARTS(STR(?axiom), "http://www.w3.org/"))
+}
\ No newline at end of file
-- 
GitLab


From a7c388e014fdcf6772bdfa8f3d0ab754826240ed Mon Sep 17 00:00:00 2001
From: Ralf Klammer <ralf.klammer@tu-dresden.de>
Date: Wed, 19 Mar 2025 08:49:18 +0100
Subject: [PATCH 33/59] [metrics] Resort/-name complexity metric docs

---
 .../schema_complexity_metrics/{01_overview.md => 00_overview.md}  | 0
 .../schema_complexity_metrics/{02_classes.md => 01_classes.md}    | 0
 .../{03_properties.md => 02_properties.md}                        | 0
 .../schema_complexity_metrics/{04_depth.md => 03_depth.md}        | 0
 .../schema_complexity_metrics/{05_width.md => 04_width.md}        | 0
 .../{06_restrictions.md => 05_restrictions.md}                    | 0
 .../schema_complexity_metrics/{07_axioms.md => 06_axioms.md}      | 0
 7 files changed, 0 insertions(+), 0 deletions(-)
 rename docs/metrics/schema_complexity_metrics/{01_overview.md => 00_overview.md} (100%)
 rename docs/metrics/schema_complexity_metrics/{02_classes.md => 01_classes.md} (100%)
 rename docs/metrics/schema_complexity_metrics/{03_properties.md => 02_properties.md} (100%)
 rename docs/metrics/schema_complexity_metrics/{04_depth.md => 03_depth.md} (100%)
 rename docs/metrics/schema_complexity_metrics/{05_width.md => 04_width.md} (100%)
 rename docs/metrics/schema_complexity_metrics/{06_restrictions.md => 05_restrictions.md} (100%)
 rename docs/metrics/schema_complexity_metrics/{07_axioms.md => 06_axioms.md} (100%)

diff --git a/docs/metrics/schema_complexity_metrics/01_overview.md b/docs/metrics/schema_complexity_metrics/00_overview.md
similarity index 100%
rename from docs/metrics/schema_complexity_metrics/01_overview.md
rename to docs/metrics/schema_complexity_metrics/00_overview.md
diff --git a/docs/metrics/schema_complexity_metrics/02_classes.md b/docs/metrics/schema_complexity_metrics/01_classes.md
similarity index 100%
rename from docs/metrics/schema_complexity_metrics/02_classes.md
rename to docs/metrics/schema_complexity_metrics/01_classes.md
diff --git a/docs/metrics/schema_complexity_metrics/03_properties.md b/docs/metrics/schema_complexity_metrics/02_properties.md
similarity index 100%
rename from docs/metrics/schema_complexity_metrics/03_properties.md
rename to docs/metrics/schema_complexity_metrics/02_properties.md
diff --git a/docs/metrics/schema_complexity_metrics/04_depth.md b/docs/metrics/schema_complexity_metrics/03_depth.md
similarity index 100%
rename from docs/metrics/schema_complexity_metrics/04_depth.md
rename to docs/metrics/schema_complexity_metrics/03_depth.md
diff --git a/docs/metrics/schema_complexity_metrics/05_width.md b/docs/metrics/schema_complexity_metrics/04_width.md
similarity index 100%
rename from docs/metrics/schema_complexity_metrics/05_width.md
rename to docs/metrics/schema_complexity_metrics/04_width.md
diff --git a/docs/metrics/schema_complexity_metrics/06_restrictions.md b/docs/metrics/schema_complexity_metrics/05_restrictions.md
similarity index 100%
rename from docs/metrics/schema_complexity_metrics/06_restrictions.md
rename to docs/metrics/schema_complexity_metrics/05_restrictions.md
diff --git a/docs/metrics/schema_complexity_metrics/07_axioms.md b/docs/metrics/schema_complexity_metrics/06_axioms.md
similarity index 100%
rename from docs/metrics/schema_complexity_metrics/07_axioms.md
rename to docs/metrics/schema_complexity_metrics/06_axioms.md
-- 
GitLab


From 3135539a6781c492d33f31630d148ea9d90492d6 Mon Sep 17 00:00:00 2001
From: Ralf Klammer <ralf.klammer@tu-dresden.de>
Date: Wed, 19 Mar 2025 09:20:36 +0100
Subject: [PATCH 34/59] [metrics] More renaming + refinement of
 metrics/index.md

---
 docs/macros/resource_metrics.md               | 10 +--
 .../01_instances.md                           |  0
 .../02_assertions.md                          |  0
 .../03_linkage_degree.md                      |  0
 .../04_edges_incoming.md                      |  0
 .../05_edges_outgoing.md                      |  0
 docs/metrics/index.md                         | 84 +++++++++++++++++--
 .../aggregator.md                             |  0
 .../article_lhb.md                            |  0
 .../data_service.md                           |  0
 .../dataset.md                                |  0
 .../learning_resource.md                      |  0
 .../organization.md                           |  0
 .../person.md                                 |  0
 .../publication.md                            |  0
 .../registry.md                               |  0
 .../repository.md                             |  0
 .../service.md                                |  0
 .../software.md                               |  0
 .../standards.md                              |  0
 .../schema_complexity_metrics/00_overview.md  | 60 -------------
 21 files changed, 82 insertions(+), 72 deletions(-)
 rename docs/metrics/{general metrics => general_metrics}/01_instances.md (100%)
 rename docs/metrics/{general metrics => general_metrics}/02_assertions.md (100%)
 rename docs/metrics/{general metrics => general_metrics}/03_linkage_degree.md (100%)
 rename docs/metrics/{general metrics => general_metrics}/04_edges_incoming.md (100%)
 rename docs/metrics/{general metrics => general_metrics}/05_edges_outgoing.md (100%)
 rename docs/metrics/{resource metrics => resource_metrics}/aggregator.md (100%)
 rename docs/metrics/{resource metrics => resource_metrics}/article_lhb.md (100%)
 rename docs/metrics/{resource metrics => resource_metrics}/data_service.md (100%)
 rename docs/metrics/{resource metrics => resource_metrics}/dataset.md (100%)
 rename docs/metrics/{resource metrics => resource_metrics}/learning_resource.md (100%)
 rename docs/metrics/{resource metrics => resource_metrics}/organization.md (100%)
 rename docs/metrics/{resource metrics => resource_metrics}/person.md (100%)
 rename docs/metrics/{resource metrics => resource_metrics}/publication.md (100%)
 rename docs/metrics/{resource metrics => resource_metrics}/registry.md (100%)
 rename docs/metrics/{resource metrics => resource_metrics}/repository.md (100%)
 rename docs/metrics/{resource metrics => resource_metrics}/service.md (100%)
 rename docs/metrics/{resource metrics => resource_metrics}/software.md (100%)
 rename docs/metrics/{resource metrics => resource_metrics}/standards.md (100%)
 delete mode 100644 docs/metrics/schema_complexity_metrics/00_overview.md

diff --git a/docs/macros/resource_metrics.md b/docs/macros/resource_metrics.md
index 42bfcd3..de24e0c 100644
--- a/docs/macros/resource_metrics.md
+++ b/docs/macros/resource_metrics.md
@@ -9,7 +9,7 @@ Resource type: {{resource_type_uri}}
 
 ### Number of Entitis
 
-see also: [general metrics/instances](/metrics/general%20metrics/01_instances/)
+see also: [general_metricsinstances](/metrics/general%20metrics/01_instances/)
 
 <i id="RM001_instances_template.rq">file: RM001_instances_template.rq</i>
 
@@ -26,7 +26,7 @@ see also: [general metrics/instances](/metrics/general%20metrics/01_instances/)
 
 ### Number of Assertions
 
-see also: [general metrics/assortions](/metrics/general%20metrics/02_assertions/)
+see also: [general_metricsassortions](/metrics/general%20metrics/02_assertions/)
 
 <i id="RM002_assertions_template.rq">file: RM002_assertions_template.rq</i>
 
@@ -36,7 +36,7 @@ see also: [general metrics/assortions](/metrics/general%20metrics/02_assertions/
 
 ### Average linkage
 
-see also: [general metrics/linkage](/metrics/general%20metrics/03_linkage_degree/)
+see also: [general_metricslinkage](/metrics/general%20metrics/03_linkage_degree/)
 
 <i id="RM003_linkage_template.rq">file: RM003_linkage_template.rq</i>
 
@@ -46,7 +46,7 @@ see also: [general metrics/linkage](/metrics/general%20metrics/03_linkage_degree
 
 ### Outgoing Edges Statistics
 
-see also: [general metrics/outgoing edges](/metrics/general%20metrics/04_outgoing_edges/)
+see also: [general_metricsoutgoing edges](/metrics/general%20metrics/04_outgoing_edges/)
 
 #### Total outgoing edges
 
@@ -82,7 +82,7 @@ see also: [general metrics/outgoing edges](/metrics/general%20metrics/04_outgoin
 
 ### Incoming Edges Statistics
 
-see also: [general metrics/incoming edges](/metrics/general%20metrics/05_incoming_edges/)
+see also: [general_metricsincoming edges](/metrics/general%20metrics/05_incoming_edges/)
 
 #### Total incoming edges
 
diff --git a/docs/metrics/general metrics/01_instances.md b/docs/metrics/general_metrics/01_instances.md
similarity index 100%
rename from docs/metrics/general metrics/01_instances.md
rename to docs/metrics/general_metrics/01_instances.md
diff --git a/docs/metrics/general metrics/02_assertions.md b/docs/metrics/general_metrics/02_assertions.md
similarity index 100%
rename from docs/metrics/general metrics/02_assertions.md
rename to docs/metrics/general_metrics/02_assertions.md
diff --git a/docs/metrics/general metrics/03_linkage_degree.md b/docs/metrics/general_metrics/03_linkage_degree.md
similarity index 100%
rename from docs/metrics/general metrics/03_linkage_degree.md
rename to docs/metrics/general_metrics/03_linkage_degree.md
diff --git a/docs/metrics/general metrics/04_edges_incoming.md b/docs/metrics/general_metrics/04_edges_incoming.md
similarity index 100%
rename from docs/metrics/general metrics/04_edges_incoming.md
rename to docs/metrics/general_metrics/04_edges_incoming.md
diff --git a/docs/metrics/general metrics/05_edges_outgoing.md b/docs/metrics/general_metrics/05_edges_outgoing.md
similarity index 100%
rename from docs/metrics/general metrics/05_edges_outgoing.md
rename to docs/metrics/general_metrics/05_edges_outgoing.md
diff --git a/docs/metrics/index.md b/docs/metrics/index.md
index 801ddcc..ec6f879 100644
--- a/docs/metrics/index.md
+++ b/docs/metrics/index.md
@@ -6,20 +6,90 @@ The NFDI4Earth Knowledge Graph metrics provide quantitative insights into our se
 
 These metrics analyze the entire knowledge graph structure and provide insights into:
 
-- Overall size and complexity
-- Connection patterns
-- Graph density and distribution
-- Edge statistics (incoming/outgoing)
+- [Overall size and complexity](general_metrics/01_instances)
+- [Graph density and distribution](general_metrics/02_assertions)
+- [Linkage](general_metrics/03_linkage_degree)
+- Edge statistics ([incoming](general_metrics/04_edges_incoming)/[outgoing](general_metrics/05_edges_outgoing))
 
 {{ metrics_table_overview_general() }}
 
-## Resource-specific Metrics
+## Resource specific Metrics
 
-These metrics focus on specific resource types within the knowledge graph:
+These metrics focus on specific resource types within the knowledge graph, analyzing key entities in the earth science domain:
+
+- Research outputs ([datasets](resource_metrics/dataset), [publications](resource_metrics/publication), [articles](resource_metrics/article_lhb))
+- Infrastructure components ([repositories](resource_metrics/repository), [services](resource_metrics/service), [software](resource_metrics/software))
+- [Learning materials and standards](resource_metrics/learning_resource)
+- [Organizations](resource_metrics/organization) and [people](resource_metrics/person)
+- Digital resources ([data services](resource_metrics/data_service), [registries](resource_metrics/registry), [aggregators](resource_metrics/aggregator))
+
+Each resource type is analyzed individually to understand its representation, completeness, and interconnections within the knowledge graph:
 
 {{ metrics_table_overview_resource() }}
 
-{{ include_if_exists("docs/metrics/schema_complexity_metrics/01_overview.md") }}
+## Schema Complexity Metrics
+
+To calculate the complexity of RDF schemas, we combine the presented formality metrics focusing on both structural and semantic aspects.
+
+### 1. Basic Structural Complexity
+
+These metrics measure the size and diversity of the RDF schema:
+
+- **[Number of classes](./schema_complexity_metrics/01_classes)** (_C_) → More classes indicate a more complex schema.
+- **[Number of properties](./schema_complexity_metrics/02_properties)** (_P_) → More properties mean more relationships between entities.
+- **[Average class hierarchy depth](./schema_complexity_metrics/03_depth)** (_D_avg_) → The mean number of hierarchy levels in the schema.
+- **[Average class hierarchy width](./schema_complexity_metrics/04_width)** (_W_avg_) → The mean number of sibling classes at each hierarchy level.
+
+A structural complexity score can be calculated as:
+
+    C_structural = w1 * C + w2 * P + w3 * D_avg + w4 * W_avg
+
+where _w_i_ are weights that determine the relative importance of each factor.
+
+### 2. Semantic Complexity
+
+If OWL is used, the complexity increases due to advanced semantics:
+
+- **[Number of restrictions](./schema_complexity_metrics/05_restrictions)** (_R_) → More restrictions indicate more constraints and rules.
+- **[Number of logical axioms](./schema_complexity_metrics/06_axioms)** (_A_) → More axioms mean more logical statements and inferences.
+
+A semantic complexity score can be calculated as:
+
+    C_semantic = w5 * R + w6 * A
+
+where _w_i_ are weights that determine the relative importance of each factor.
+
+### 3. Combined Formula for Schema Complexity
+
+To create an overall complexity score, we can combine both structural and semantic aspects:
+
+    C_schema = w1 * C + w2 * P + w3 * D_avg + w4 * W_avg + w5 * R + w6 * A
+
+where _w_i_ are weights that determine the relative importance of each factor.
+
+This formula provides a single numerical value representing schema complexity, which can be normalized (e.g., 0–100) for comparison across different RDF schemas.
+
+### References
+
+> !!!Not the final references!!!
+
+1. Structural Metrics for Ontologies:
+
+- Gómez-Pérez et al. (2004) – "Evaluation of Ontologies"
+- Describes metrics like number of classes, hierarchy depth, and relations
+- Source: https://link.springer.com/chapter/10.1007/978-3-540-30202-5_3
+
+2. OWL and Schema Complexity Measurements:
+
+   - Tartir & Arpinar (2010) – "Ontology Evaluation and Ranking using OntoQA"
+   - Develops the OntoQA model combining structural and semantic metrics
+   - Source: https://doi.org/10.1109/ICDEW.2005.43
+
+3. SPARQL Analysis and RDF Complexity:
+
+   - Lanzenberger et al. (2008) – "Ontology Evaluation – State of the Art"
+   - Describes hierarchical depth as key metric for RDF schema complexity
+   - Source: https://doi.org/10.1007/978-3-540-92673-3_10
 
 ## About the Metrics
 
diff --git a/docs/metrics/resource metrics/aggregator.md b/docs/metrics/resource_metrics/aggregator.md
similarity index 100%
rename from docs/metrics/resource metrics/aggregator.md
rename to docs/metrics/resource_metrics/aggregator.md
diff --git a/docs/metrics/resource metrics/article_lhb.md b/docs/metrics/resource_metrics/article_lhb.md
similarity index 100%
rename from docs/metrics/resource metrics/article_lhb.md
rename to docs/metrics/resource_metrics/article_lhb.md
diff --git a/docs/metrics/resource metrics/data_service.md b/docs/metrics/resource_metrics/data_service.md
similarity index 100%
rename from docs/metrics/resource metrics/data_service.md
rename to docs/metrics/resource_metrics/data_service.md
diff --git a/docs/metrics/resource metrics/dataset.md b/docs/metrics/resource_metrics/dataset.md
similarity index 100%
rename from docs/metrics/resource metrics/dataset.md
rename to docs/metrics/resource_metrics/dataset.md
diff --git a/docs/metrics/resource metrics/learning_resource.md b/docs/metrics/resource_metrics/learning_resource.md
similarity index 100%
rename from docs/metrics/resource metrics/learning_resource.md
rename to docs/metrics/resource_metrics/learning_resource.md
diff --git a/docs/metrics/resource metrics/organization.md b/docs/metrics/resource_metrics/organization.md
similarity index 100%
rename from docs/metrics/resource metrics/organization.md
rename to docs/metrics/resource_metrics/organization.md
diff --git a/docs/metrics/resource metrics/person.md b/docs/metrics/resource_metrics/person.md
similarity index 100%
rename from docs/metrics/resource metrics/person.md
rename to docs/metrics/resource_metrics/person.md
diff --git a/docs/metrics/resource metrics/publication.md b/docs/metrics/resource_metrics/publication.md
similarity index 100%
rename from docs/metrics/resource metrics/publication.md
rename to docs/metrics/resource_metrics/publication.md
diff --git a/docs/metrics/resource metrics/registry.md b/docs/metrics/resource_metrics/registry.md
similarity index 100%
rename from docs/metrics/resource metrics/registry.md
rename to docs/metrics/resource_metrics/registry.md
diff --git a/docs/metrics/resource metrics/repository.md b/docs/metrics/resource_metrics/repository.md
similarity index 100%
rename from docs/metrics/resource metrics/repository.md
rename to docs/metrics/resource_metrics/repository.md
diff --git a/docs/metrics/resource metrics/service.md b/docs/metrics/resource_metrics/service.md
similarity index 100%
rename from docs/metrics/resource metrics/service.md
rename to docs/metrics/resource_metrics/service.md
diff --git a/docs/metrics/resource metrics/software.md b/docs/metrics/resource_metrics/software.md
similarity index 100%
rename from docs/metrics/resource metrics/software.md
rename to docs/metrics/resource_metrics/software.md
diff --git a/docs/metrics/resource metrics/standards.md b/docs/metrics/resource_metrics/standards.md
similarity index 100%
rename from docs/metrics/resource metrics/standards.md
rename to docs/metrics/resource_metrics/standards.md
diff --git a/docs/metrics/schema_complexity_metrics/00_overview.md b/docs/metrics/schema_complexity_metrics/00_overview.md
deleted file mode 100644
index 9bb98a6..0000000
--- a/docs/metrics/schema_complexity_metrics/00_overview.md
+++ /dev/null
@@ -1,60 +0,0 @@
-# Overview - Schema Complexity Metrics
-
-To calculate the complexity of RDF schemas, we combine the presented formality metrics focusing on both structural and semantic aspects.
-
-## 1. Basic Structural Complexity
-
-These metrics measure the size and diversity of the RDF schema:
-
-- **[Number of classes](classes.md)** (_C_) → More classes indicate a more complex schema.
-- **[Number of properties](properties.md)** (_P_) → More properties mean more relationships between entities.
-- **[Average class hierarchy depth](depth.md)** (_D_avg_) → The mean number of hierarchy levels in the schema.
-- **[Average class hierarchy width](width.md)** (_W_avg_) → The mean number of sibling classes at each hierarchy level.
-
-A structural complexity score can be calculated as:
-
-    C_structural = w1 * C + w2 * P + w3 * D_avg + w4 * W_avg
-
-where _w_i_ are weights that determine the relative importance of each factor.
-
-## 2. Semantic Complexity
-
-If OWL is used, the complexity increases due to advanced semantics:
-
-- **[Number of restrictions](restrictions.md)** (_R_) → More restrictions indicate more constraints and rules.
-- **[Number of logical axioms](axioms.md)** (_A_) → More axioms mean more logical statements and inferences.
-
-A semantic complexity score can be calculated as:
-
-    C_semantic = w5 * R + w6 * A
-
-where _w_i_ are weights that determine the relative importance of each factor.
-
-## 3. Combined Formula for Schema Complexity
-
-To create an overall complexity score, we can combine both structural and semantic aspects:
-
-    C_schema = w1 * C + w2 * P + w3 * D_avg + w4 * W_avg + w5 * R + w6 * A
-
-where _w_i_ are weights that determine the relative importance of each factor.
-
-This formula provides a single numerical value representing schema complexity, which can be normalized (e.g., 0–100) for comparison across different RDF schemas.
-
-## References
-
-> !!!Not the final references!!!
-
-1. Structural Metrics for Ontologies
-   - Gómez-Pérez et al. (2004) – "Evaluation of Ontologies"
-   - Describes metrics like number of classes, hierarchy depth, and relations
-   - Source: https://link.springer.com/chapter/10.1007/978-3-540-30202-5_3
-
-2. OWL and Schema Complexity Measurements
-   - Tartir & Arpinar (2010) – "Ontology Evaluation and Ranking using OntoQA"
-   - Develops the OntoQA model combining structural and semantic metrics
-   - Source: https://doi.org/10.1109/ICDEW.2005.43
-
-3. SPARQL Analysis and RDF Complexity
-   - Lanzenberger et al. (2008) – "Ontology Evaluation – State of the Art"
-   - Describes hierarchical depth as key metric for RDF schema complexity
-   - Source: https://doi.org/10.1007/978-3-540-92673-3_10
\ No newline at end of file
-- 
GitLab


From 397313a3fe3a5de5c2017142972b1c639545e4ad Mon Sep 17 00:00:00 2001
From: Ralf Klammer <ralf.klammer@tu-dresden.de>
Date: Wed, 19 Mar 2025 14:12:50 +0100
Subject: [PATCH 35/59] [metrics] Add automatic stuff for complexity metrics

---
 docs/macros/main.py                          | 20 +++++
 docs/macros/table_renderer.py                | 75 ++++++++++++++++++-
 docs/metrics/index.md                        |  8 ++
 scripts/kg_analysis/interfaces/__init__.py   |  1 +
 scripts/kg_analysis/interfaces/complexity.py | 77 ++++++++++++++++++++
 scripts/kg_analysis/metrics_runner.py        | 70 +++++++++++++++++-
 6 files changed, 246 insertions(+), 5 deletions(-)
 create mode 100644 scripts/kg_analysis/interfaces/complexity.py

diff --git a/docs/macros/main.py b/docs/macros/main.py
index 64a249d..4789dae 100644
--- a/docs/macros/main.py
+++ b/docs/macros/main.py
@@ -94,3 +94,23 @@ def define_env(env):
                 return renderer.render("general")
         except (FileNotFoundError, json.JSONDecodeError) as e:
             return f"*Error loading metrics: {e}*"
+
+    @env.macro
+    def metrics_table_overview_complexity():
+        try:
+            with open(Path("reports/metrics/complexity.json"), "r") as f:
+                renderer = MetricsTableRenderer(json.load(f))
+                return renderer.render("complexity")
+        except (FileNotFoundError, json.JSONDecodeError) as e:
+            return f"*Error loading metrics: {e}*"
+
+    @env.macro
+    def metrics_table_single_complexity(metric_key):
+        try:
+            with open(
+                Path("reports/metrics/complexity.json"), "r", encoding="utf-8"
+            ) as f:
+                renderer = MetricsTableRenderer(json.load(f))
+                return renderer.render("complexity", metric_key=metric_key)
+        except (FileNotFoundError, json.JSONDecodeError) as e:
+            return f"*Error loading metrics: {e}*"
diff --git a/docs/macros/table_renderer.py b/docs/macros/table_renderer.py
index da8ea24..a330a20 100644
--- a/docs/macros/table_renderer.py
+++ b/docs/macros/table_renderer.py
@@ -12,7 +12,7 @@ class MetricsTableRenderer:
         self.timestamp = data.get("timestamp", "unknown")
         self.output = []
 
-    def render(self, table_type="general", resource_type=None):
+    def render(self, table_type="general", resource_type=None, metric_key=None):
         """
         Renders the table in the desired format.
 
@@ -31,6 +31,7 @@ class MetricsTableRenderer:
             "resource": lambda: self._render_resource(resource_type),
             "general_overview": self._render_general_overview,
             "general": self._render_general,
+            "complexity": lambda: self._render_complexity(metric_key),
         }
 
         # Get the appropriate rendering method based on the table type
@@ -38,7 +39,7 @@ class MetricsTableRenderer:
         render_method()
 
         # Append the timestamp to the output
-        self.output.append(f"\n*Last updated: {self.timestamp}*")
+        self.output.append(f"\n*Last updated: {self.timestamp}*\n")
         return "\n".join(self.output)
 
     def _render_resource_overview(self):
@@ -134,7 +135,6 @@ class MetricsTableRenderer:
         for metric_key, metric_data in self.data.items():
             if metric_key == "timestamp":
                 continue
-
             if isinstance(metric_data, dict):
                 if "files" in metric_data:
                     self.output.append(f"| **{metric_data['name']}** | | |")
@@ -146,3 +146,72 @@ class MetricsTableRenderer:
                     self.output.append(
                         f"| {metric_data['name']} | {metric_data['file']} | {metric_data.get('result', '-')} |"
                     )
+
+    def _render_complexity(self, cmplxty_type=None):
+        """Renders a complexity table.
+
+        Args:
+            cmplxty_type (str, optional): Specific complexity type.
+                If None, an overview of all types is created.
+        """
+        # Base header for all tables
+        header = ["Metric", "Query", "Value", "Weight", "Weighted Value"]
+
+        # Add Category column for overview
+        if not cmplxty_type:
+            header += ["Category"]
+
+        # Create table header
+        self.output.append("| " + " | ".join(header) + " |")
+        self.output.append("| " + " | ".join(["---" for _ in header]) + " |")
+
+        # Select categories based on type
+        categories = {}
+        if cmplxty_type:
+            # Search directly or in nested structures
+            if cmplxty_type in self.data:
+                categories[cmplxty_type] = self.data[cmplxty_type]
+            else:
+                for value in self.data.values():
+                    if (
+                        isinstance(value, dict)
+                        and "files" in value
+                        and cmplxty_type in value["files"]
+                    ):
+                        categories[cmplxty_type] = {
+                            "name": cmplxty_type,
+                            "files": {
+                                cmplxty_type: value["files"][cmplxty_type],
+                            },
+                        }
+                        break
+        else:
+            # Include all categories except the timestamp
+            categories = {k: v for k, v in self.data.items() if k != "timestamp"}
+
+        # Iterate through the categories
+        for data in categories.values():
+            if "files" not in data:
+                continue
+
+            # Category header with appropriate number of empty columns
+            empty_cols = len(header) - 1
+            if not cmplxty_type:
+                self.output.append(
+                    f"| **{data['name']}** | " + " | " * empty_cols + "|"
+                )
+
+            for metric_data in data["files"].values():
+                row = [
+                    str(metric_data["name"]),
+                    str(metric_data.get("file", "-")),
+                    str(metric_data.get("result", "-")),
+                    str(metric_data.get("weight", "-")),
+                    str(metric_data.get("weighted_value", "-")),
+                ]
+
+                # Add Category column for overview
+                if not cmplxty_type:
+                    row.insert(0, "")
+
+                self.output.append("| " + " | ".join(row) + " |")
diff --git a/docs/metrics/index.md b/docs/metrics/index.md
index ec6f879..064667b 100644
--- a/docs/metrics/index.md
+++ b/docs/metrics/index.md
@@ -11,6 +11,8 @@ These metrics analyze the entire knowledge graph structure and provide insights
 - [Linkage](general_metrics/03_linkage_degree)
 - Edge statistics ([incoming](general_metrics/04_edges_incoming)/[outgoing](general_metrics/05_edges_outgoing))
 
+### Results
+
 {{ metrics_table_overview_general() }}
 
 ## Resource specific Metrics
@@ -25,6 +27,8 @@ These metrics focus on specific resource types within the knowledge graph, analy
 
 Each resource type is analyzed individually to understand its representation, completeness, and interconnections within the knowledge graph:
 
+### Results
+
 {{ metrics_table_overview_resource() }}
 
 ## Schema Complexity Metrics
@@ -69,6 +73,10 @@ where _w_i_ are weights that determine the relative importance of each factor.
 
 This formula provides a single numerical value representing schema complexity, which can be normalized (e.g., 0–100) for comparison across different RDF schemas.
 
+### Results
+
+{{ metrics_table_overview_complexity() }}
+
 ### References
 
 > !!!Not the final references!!!
diff --git a/scripts/kg_analysis/interfaces/__init__.py b/scripts/kg_analysis/interfaces/__init__.py
index 79f526c..c223999 100644
--- a/scripts/kg_analysis/interfaces/__init__.py
+++ b/scripts/kg_analysis/interfaces/__init__.py
@@ -1,3 +1,4 @@
+from .complexity import query_templates as complexity_query_templates
 from .general import query_templates as general_query_templates
 from .resources import query_templates as resource_query_templates
 from .resources import resource_types
diff --git a/scripts/kg_analysis/interfaces/complexity.py b/scripts/kg_analysis/interfaces/complexity.py
new file mode 100644
index 0000000..2d98193
--- /dev/null
+++ b/scripts/kg_analysis/interfaces/complexity.py
@@ -0,0 +1,77 @@
+# -*- coding: utf-8 -*-
+# Copyright (C) 2025 TU-Dresden, ZIH
+# ralf.klammer@tu-dresden.de
+import logging
+
+log = logging.getLogger(__name__)
+
+query_templates = {
+    "structural_complexity": {
+        "name": "Structural Complexity",
+        "files": {
+            "classes": {
+                "name": "Number of Classes",
+                "file": "RF_001.rq",
+                "execute": True,
+                "weight": 0.2,
+            },
+            "properties": {
+                "name": "Number of properties",
+                "file": "RF_002_1.rq",
+                "execute": True,
+                "weight": 0.2,
+            },
+            "depth": {
+                "name": "Average class hierarchy depth",
+                "file": "RF_003.rq",
+                "execute": True,
+                "weight": 1,
+            },
+            "width": {
+                "name": "Average class hierarchy width",
+                "file": "RF_004.rq",
+                "execute": True,
+                "weight": 1,
+            },
+        },
+    },
+    "semantic_complexity": {
+        "name": "Semantic Complexity",
+        "files": {
+            "restrictions": {
+                "name": "Number of restrictions",
+                "file": "RF_005.rq",
+                "execute": True,
+                "weight": 0.2,
+            },
+            "axioms": {
+                "name": "Number of logical axioms",
+                "file": "RF_006.rq",
+                "execute": True,
+                "weight": 1,
+            },
+        },
+    },
+    "schematic_complexity": {
+        "name": "Schematic Complexity",
+        "files": {
+            "overall_complexity": {
+                "execute": False,
+                "name": "Overall Complexity",
+                "result": 0,
+            },
+            "structural_complexity": {
+                "name": "Structural Complexity",
+                "execute": False,
+                "result": 0,
+                "weight": 1,
+            },
+            "semantic_complexity": {
+                "name": "Semantic Complexity",
+                "execute": False,
+                "result": 0,
+                "weight": 1,
+            },
+        },
+    },
+}
diff --git a/scripts/kg_analysis/metrics_runner.py b/scripts/kg_analysis/metrics_runner.py
index 48b4141..8f12e9d 100644
--- a/scripts/kg_analysis/metrics_runner.py
+++ b/scripts/kg_analysis/metrics_runner.py
@@ -14,6 +14,7 @@ from typing import Optional
 
 from .query_runner import QueryRunner
 from .interfaces import (
+    complexity_query_templates,
     general_query_templates,
     resource_query_templates,
     resource_types,
@@ -252,6 +253,68 @@ class MetricsRunner_Resources(MetricsRunnerBase):
         return results
 
 
+class MetricsRunner_Complexity(MetricsRunnerBase):
+
+    query_templates = complexity_query_templates
+
+    def _calculate_category_complexity(self, category_data):
+        """Calculates the weighted complexity for a category"""
+        total = 0.0
+        for metric, data in category_data["files"].items():
+            if "result" in data and data["result"] is not None:
+                print(data)
+                try:
+                    value = float(data["result"])
+                    weight = float(data.get("weight", 1.0))
+                    weighted_value = value * weight
+                    data["weighted_value"] = weighted_value
+                    total += weighted_value
+                except (ValueError, TypeError) as e:
+                    log.error(f"Error calculating {metric}: {e}")
+        return total
+
+    def run(self):
+        """
+        Executes complexity metrics and calculates the overall complexity.
+        This method runs the complexity metrics, calculates the weighted
+        complexity for each category, and updates the overall complexity.
+        Finally, it saves the results to a JSON file.
+
+        Returns:
+            dict: Complexity metrics results
+        """
+        # Execute the base run method to get initial query results
+        queries = super().run()
+
+        # Extract schematic complexity data
+        schematic_complexity = queries["schematic_complexity"]
+
+        # Calculate complexity for each category and update overall complexity
+        for category in [
+            category
+            for category in ["structural_complexity", "semantic_complexity"]
+        ]:
+            # Calculate the weighted complexity for the current category
+            result = self._calculate_category_complexity(queries[category])
+            # Update the result for the current category
+            schematic_complexity["files"][category]["result"] = result
+            schematic_complexity["files"][category]["weighted_value"] = (
+                result * schematic_complexity["files"][category]["weight"]
+            )
+            # Update the overall complexity with the weighted result
+            schematic_complexity["files"]["overall_complexity"][
+                "result"
+            ] += schematic_complexity["files"][category]["weighted_value"]
+
+        # Update the schematic complexity in the queries dictionary
+        queries["schematic_complexity"] = schematic_complexity
+
+        # Save the results to a JSON file
+        self.save_to_json(queries, "reports/metrics/complexity.json")
+
+        return queries
+
+
 class MetricsRunner(ABC):
     """Main entry point for executing all metrics"""
 
@@ -260,7 +323,10 @@ class MetricsRunner(ABC):
         Executes both general and resource-specific metrics
         """
         # Run general metrics for entire knowledge graph
-        MetricsRunner_General().run()
+        # MetricsRunner_General().run()
 
         # Run metrics for specific resource types
-        MetricsRunner_Resources().run()
+        # MetricsRunner_Resources().run()
+
+        # Run metrics on schematic complexity
+        MetricsRunner_Complexity().run()
-- 
GitLab


From 8501ae8d0dd93cbfd533aadd3d1fb29dbcf9df96 Mon Sep 17 00:00:00 2001
From: Ralf Klammer <ralf.klammer@tu-dresden.de>
Date: Wed, 19 Mar 2025 14:14:10 +0100
Subject: [PATCH 36/59] [metrics] Adain renaming file structure

---
 docs/metrics/general metrics/00_overview.md   | 10 ++
 .../01_instances.md                           |  0
 .../02_assertions.md                          |  0
 .../03_linkage_degree.md                      |  0
 .../04_edges_incoming.md                      |  0
 .../05_edges_outgoing.md                      |  0
 .../aggregator.md                             |  0
 .../article_lhb.md                            |  0
 .../data_service.md                           |  0
 .../dataset.md                                |  0
 .../learning_resource.md                      |  0
 .../organization.md                           |  0
 .../person.md                                 |  0
 .../publication.md                            |  0
 .../registry.md                               |  0
 .../repository.md                             |  0
 .../service.md                                |  0
 .../software.md                               |  0
 .../standards.md                              |  0
 .../schema_complexity_metrics/00_overview.md  | 63 +++++++++++++
 .../schema_complexity_metrics/01_classes.md   |  4 +
 .../02_properties.md                          |  4 +
 .../schema_complexity_metrics/03_depth.md     |  5 +
 .../schema_complexity_metrics/04_width.md     |  4 +
 .../05_restrictions.md                        |  4 +
 .../schema_complexity_metrics/06_axioms.md    |  4 +
 queries/metrics/RF_004.rq                     |  2 +-
 queries/metrics/RF_005.rq                     | 54 +++++------
 queries/metrics/RF_006.rq                     | 46 +++-------
 queries/metrics/RF_007.rq                     | 21 -----
 reports/metrics/complexity.json               | 91 +++++++++++++++++++
 31 files changed, 233 insertions(+), 79 deletions(-)
 create mode 100644 docs/metrics/general metrics/00_overview.md
 rename docs/metrics/{general_metrics => general metrics}/01_instances.md (100%)
 rename docs/metrics/{general_metrics => general metrics}/02_assertions.md (100%)
 rename docs/metrics/{general_metrics => general metrics}/03_linkage_degree.md (100%)
 rename docs/metrics/{general_metrics => general metrics}/04_edges_incoming.md (100%)
 rename docs/metrics/{general_metrics => general metrics}/05_edges_outgoing.md (100%)
 rename docs/metrics/{resource_metrics => resource metrics}/aggregator.md (100%)
 rename docs/metrics/{resource_metrics => resource metrics}/article_lhb.md (100%)
 rename docs/metrics/{resource_metrics => resource metrics}/data_service.md (100%)
 rename docs/metrics/{resource_metrics => resource metrics}/dataset.md (100%)
 rename docs/metrics/{resource_metrics => resource metrics}/learning_resource.md (100%)
 rename docs/metrics/{resource_metrics => resource metrics}/organization.md (100%)
 rename docs/metrics/{resource_metrics => resource metrics}/person.md (100%)
 rename docs/metrics/{resource_metrics => resource metrics}/publication.md (100%)
 rename docs/metrics/{resource_metrics => resource metrics}/registry.md (100%)
 rename docs/metrics/{resource_metrics => resource metrics}/repository.md (100%)
 rename docs/metrics/{resource_metrics => resource metrics}/service.md (100%)
 rename docs/metrics/{resource_metrics => resource metrics}/software.md (100%)
 rename docs/metrics/{resource_metrics => resource metrics}/standards.md (100%)
 create mode 100644 docs/metrics/schema_complexity_metrics/00_overview.md
 delete mode 100644 queries/metrics/RF_007.rq
 create mode 100644 reports/metrics/complexity.json

diff --git a/docs/metrics/general metrics/00_overview.md b/docs/metrics/general metrics/00_overview.md
new file mode 100644
index 0000000..cc412a3
--- /dev/null
+++ b/docs/metrics/general metrics/00_overview.md	
@@ -0,0 +1,10 @@
+# Overview
+
+These metrics analyze the entire knowledge graph structure and provide insights into:
+
+- Overall size and complexity
+- Connection patterns
+- Graph density and distribution
+- Edge statistics (incoming/outgoing)
+
+{{ metrics_table_overview_general() }}
\ No newline at end of file
diff --git a/docs/metrics/general_metrics/01_instances.md b/docs/metrics/general metrics/01_instances.md
similarity index 100%
rename from docs/metrics/general_metrics/01_instances.md
rename to docs/metrics/general metrics/01_instances.md
diff --git a/docs/metrics/general_metrics/02_assertions.md b/docs/metrics/general metrics/02_assertions.md
similarity index 100%
rename from docs/metrics/general_metrics/02_assertions.md
rename to docs/metrics/general metrics/02_assertions.md
diff --git a/docs/metrics/general_metrics/03_linkage_degree.md b/docs/metrics/general metrics/03_linkage_degree.md
similarity index 100%
rename from docs/metrics/general_metrics/03_linkage_degree.md
rename to docs/metrics/general metrics/03_linkage_degree.md
diff --git a/docs/metrics/general_metrics/04_edges_incoming.md b/docs/metrics/general metrics/04_edges_incoming.md
similarity index 100%
rename from docs/metrics/general_metrics/04_edges_incoming.md
rename to docs/metrics/general metrics/04_edges_incoming.md
diff --git a/docs/metrics/general_metrics/05_edges_outgoing.md b/docs/metrics/general metrics/05_edges_outgoing.md
similarity index 100%
rename from docs/metrics/general_metrics/05_edges_outgoing.md
rename to docs/metrics/general metrics/05_edges_outgoing.md
diff --git a/docs/metrics/resource_metrics/aggregator.md b/docs/metrics/resource metrics/aggregator.md
similarity index 100%
rename from docs/metrics/resource_metrics/aggregator.md
rename to docs/metrics/resource metrics/aggregator.md
diff --git a/docs/metrics/resource_metrics/article_lhb.md b/docs/metrics/resource metrics/article_lhb.md
similarity index 100%
rename from docs/metrics/resource_metrics/article_lhb.md
rename to docs/metrics/resource metrics/article_lhb.md
diff --git a/docs/metrics/resource_metrics/data_service.md b/docs/metrics/resource metrics/data_service.md
similarity index 100%
rename from docs/metrics/resource_metrics/data_service.md
rename to docs/metrics/resource metrics/data_service.md
diff --git a/docs/metrics/resource_metrics/dataset.md b/docs/metrics/resource metrics/dataset.md
similarity index 100%
rename from docs/metrics/resource_metrics/dataset.md
rename to docs/metrics/resource metrics/dataset.md
diff --git a/docs/metrics/resource_metrics/learning_resource.md b/docs/metrics/resource metrics/learning_resource.md
similarity index 100%
rename from docs/metrics/resource_metrics/learning_resource.md
rename to docs/metrics/resource metrics/learning_resource.md
diff --git a/docs/metrics/resource_metrics/organization.md b/docs/metrics/resource metrics/organization.md
similarity index 100%
rename from docs/metrics/resource_metrics/organization.md
rename to docs/metrics/resource metrics/organization.md
diff --git a/docs/metrics/resource_metrics/person.md b/docs/metrics/resource metrics/person.md
similarity index 100%
rename from docs/metrics/resource_metrics/person.md
rename to docs/metrics/resource metrics/person.md
diff --git a/docs/metrics/resource_metrics/publication.md b/docs/metrics/resource metrics/publication.md
similarity index 100%
rename from docs/metrics/resource_metrics/publication.md
rename to docs/metrics/resource metrics/publication.md
diff --git a/docs/metrics/resource_metrics/registry.md b/docs/metrics/resource metrics/registry.md
similarity index 100%
rename from docs/metrics/resource_metrics/registry.md
rename to docs/metrics/resource metrics/registry.md
diff --git a/docs/metrics/resource_metrics/repository.md b/docs/metrics/resource metrics/repository.md
similarity index 100%
rename from docs/metrics/resource_metrics/repository.md
rename to docs/metrics/resource metrics/repository.md
diff --git a/docs/metrics/resource_metrics/service.md b/docs/metrics/resource metrics/service.md
similarity index 100%
rename from docs/metrics/resource_metrics/service.md
rename to docs/metrics/resource metrics/service.md
diff --git a/docs/metrics/resource_metrics/software.md b/docs/metrics/resource metrics/software.md
similarity index 100%
rename from docs/metrics/resource_metrics/software.md
rename to docs/metrics/resource metrics/software.md
diff --git a/docs/metrics/resource_metrics/standards.md b/docs/metrics/resource metrics/standards.md
similarity index 100%
rename from docs/metrics/resource_metrics/standards.md
rename to docs/metrics/resource metrics/standards.md
diff --git a/docs/metrics/schema_complexity_metrics/00_overview.md b/docs/metrics/schema_complexity_metrics/00_overview.md
new file mode 100644
index 0000000..27b45fd
--- /dev/null
+++ b/docs/metrics/schema_complexity_metrics/00_overview.md
@@ -0,0 +1,63 @@
+# Overview
+
+To calculate the complexity of RDF schemas, we combine the presented formality metrics focusing on both structural and semantic aspects.
+
+### 1. Basic Structural Complexity
+
+These metrics measure the size and diversity of the RDF schema:
+
+- **[Number of classes](./schema_complexity_metrics/01_classes)** (_C_) → More classes indicate a more complex schema.
+- **[Number of properties](./schema_complexity_metrics/02_properties)** (_P_) → More properties mean more relationships between entities.
+- **[Average class hierarchy depth](./schema_complexity_metrics/03_depth)** (_D_avg_) → The mean number of hierarchy levels in the schema.
+- **[Average class hierarchy width](./schema_complexity_metrics/04_width)** (_W_avg_) → The mean number of sibling classes at each hierarchy level.
+
+A structural complexity score can be calculated as:
+
+    C_structural = w1 * C + w2 * P + w3 * D_avg + w4 * W_avg
+
+where _w_i_ are weights that determine the relative importance of each factor.
+
+### 2. Semantic Complexity
+
+If OWL is used, the complexity increases due to advanced semantics:
+
+- **[Number of restrictions](./schema_complexity_metrics/05_restrictions)** (_R_) → More restrictions indicate more constraints and rules.
+- **[Number of logical axioms](./schema_complexity_metrics/06_axioms)** (_A_) → More axioms mean more logical statements and inferences.
+
+A semantic complexity score can be calculated as:
+
+    C_semantic = w5 * R + w6 * A
+
+where _w_i_ are weights that determine the relative importance of each factor.
+
+### 3. Combined Formula for Schema Complexity
+
+To create an overall complexity score, we can combine both structural and semantic aspects:
+
+    C_schema = w1 * C + w2 * P + w3 * D_avg + w4 * W_avg + w5 * R + w6 * A
+
+where _w_i_ are weights that determine the relative importance of each factor.
+
+This formula provides a single numerical value representing schema complexity, which can be normalized (e.g., 0–100) for comparison across different RDF schemas.
+
+### References
+
+> !!!Not the final references!!!
+
+1. Structural Metrics for Ontologies:
+
+- Gómez-Pérez et al. (2004) – "Evaluation of Ontologies"
+- Describes metrics like number of classes, hierarchy depth, and relations
+- Source: https://link.springer.com/chapter/10.1007/978-3-540-30202-5_3
+
+2. OWL and Schema Complexity Measurements:
+
+   - Tartir & Arpinar (2010) – "Ontology Evaluation and Ranking using OntoQA"
+   - Develops the OntoQA model combining structural and semantic metrics
+   - Source: https://doi.org/10.1109/ICDEW.2005.43
+
+3. SPARQL Analysis and RDF Complexity:
+
+   - Lanzenberger et al. (2008) – "Ontology Evaluation – State of the Art"
+   - Describes hierarchical depth as key metric for RDF schema complexity
+   - Source: https://doi.org/10.1007/978-3-540-92673-3_10
\ No newline at end of file
diff --git a/docs/metrics/schema_complexity_metrics/01_classes.md b/docs/metrics/schema_complexity_metrics/01_classes.md
index 2b2da15..953bbaf 100644
--- a/docs/metrics/schema_complexity_metrics/01_classes.md
+++ b/docs/metrics/schema_complexity_metrics/01_classes.md
@@ -2,6 +2,10 @@
 
 This metric counts the total number of classes defined in our schema.
 
+## Results
+
+{{ metrics_table_single_complexity('classes') }}
+
 ## SPARQL Query
 
 ```sparql
diff --git a/docs/metrics/schema_complexity_metrics/02_properties.md b/docs/metrics/schema_complexity_metrics/02_properties.md
index 6547ab8..a703c88 100644
--- a/docs/metrics/schema_complexity_metrics/02_properties.md
+++ b/docs/metrics/schema_complexity_metrics/02_properties.md
@@ -2,6 +2,10 @@
 
 This metric analyzes the properties defined in our schema through multiple aspects.
 
+## Results
+
+{{ metrics_table_single_complexity('properties') }}
+
 ## Total Properties Count
 
 ```sparql
diff --git a/docs/metrics/schema_complexity_metrics/03_depth.md b/docs/metrics/schema_complexity_metrics/03_depth.md
index 2dc4a40..671be88 100644
--- a/docs/metrics/schema_complexity_metrics/03_depth.md
+++ b/docs/metrics/schema_complexity_metrics/03_depth.md
@@ -2,6 +2,11 @@
 
 This metric calculates the average depth of the class hierarchy in the schema.
 
+## Results
+
+{{ metrics_table_single_complexity('depth') }}
+
+
 ## SPARQL Query
 
 ```sparql
diff --git a/docs/metrics/schema_complexity_metrics/04_width.md b/docs/metrics/schema_complexity_metrics/04_width.md
index 3f0c38e..5acfb26 100644
--- a/docs/metrics/schema_complexity_metrics/04_width.md
+++ b/docs/metrics/schema_complexity_metrics/04_width.md
@@ -2,6 +2,10 @@
 
 This metric calculates the average number of subclasses per class (branching factor) in the schema.
 
+## Results
+
+{{ metrics_table_single_complexity('width') }}
+
 ## SPARQL Query
 
 ```sparql
diff --git a/docs/metrics/schema_complexity_metrics/05_restrictions.md b/docs/metrics/schema_complexity_metrics/05_restrictions.md
index 4f27219..2c68bc9 100644
--- a/docs/metrics/schema_complexity_metrics/05_restrictions.md
+++ b/docs/metrics/schema_complexity_metrics/05_restrictions.md
@@ -2,6 +2,10 @@
 
 This metric counts the various types of OWL restrictions defined in the schema.
 
+## Results
+
+{{ metrics_table_single_complexity('restrictions') }}
+
 ## SPARQL Query
 
 ```sparql
diff --git a/docs/metrics/schema_complexity_metrics/06_axioms.md b/docs/metrics/schema_complexity_metrics/06_axioms.md
index 16434c5..53734f6 100644
--- a/docs/metrics/schema_complexity_metrics/06_axioms.md
+++ b/docs/metrics/schema_complexity_metrics/06_axioms.md
@@ -2,6 +2,10 @@
 
 This metric counts the various types of OWL logical axioms defined in the schema.
 
+## Results
+
+{{ metrics_table_single_complexity('axioms') }}
+
 ## SPARQL Query
 
 ```sparql
diff --git a/queries/metrics/RF_004.rq b/queries/metrics/RF_004.rq
index 51ad8ed..aa0e49d 100644
--- a/queries/metrics/RF_004.rq
+++ b/queries/metrics/RF_004.rq
@@ -1,4 +1,4 @@
-# This SPARQL query calculates the average branching factor of classes in
+# This SPARQL query calculates the average branching factor (widht) of classes in
 # an RDF dataset.
 # The branching factor is defined as the average number of direct
 # subclasses per class.
diff --git a/queries/metrics/RF_005.rq b/queries/metrics/RF_005.rq
index 003e342..b256108 100644
--- a/queries/metrics/RF_005.rq
+++ b/queries/metrics/RF_005.rq
@@ -1,38 +1,40 @@
-PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>
-PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
+# This SPARQL query retrieves various counts of OWL restrictions
+# from a dataset, excluding built-in properties
+
 PREFIX owl: <http://www.w3.org/2002/07/owl#>
+PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
 
 SELECT
-  (COUNT(DISTINCT ?class) AS ?totalClasses)
-  (COUNT(DISTINCT ?property) AS ?totalProperties)
-  (COUNT(DISTINCT ?restriction) AS ?totalRestrictions)
-  (?totalClasses + ?totalProperties + ?totalRestrictions AS ?complexityScore)
+  (COUNT(DISTINCT ?restriction) as ?totalRestrictions)
+  (COUNT(DISTINCT ?someValues) as ?someValuesFrom)
+  (COUNT(DISTINCT ?allValues) as ?allValuesFrom)
+  (COUNT(DISTINCT ?hasValue) as ?hasValueRestrictions)
+  (COUNT(DISTINCT ?cardinality) as ?cardinalityRestrictions)
 WHERE {
   {
-    # Count Classes
-    {
-      ?class a rdfs:Class .
-    } UNION {
-      ?class a owl:Class .
-    }
-    FILTER(!STRSTARTS(STR(?class), "http://www.w3.org/"))
+    ?restriction a owl:Restriction .
+    FILTER(!STRSTARTS(STR(?restriction), "http://www.w3.org/"))
   }
-  UNION
-  {
-    # Count Properties
+  OPTIONAL {
+    ?someValues a owl:Restriction ;
+                owl:someValuesFrom ?target1 .
+  }
+  OPTIONAL {
+    ?allValues a owl:Restriction ;
+               owl:allValuesFrom ?target2 .
+  }
+  OPTIONAL {
+    ?hasValue a owl:Restriction ;
+              owl:hasValue ?target3 .
+  }
+  OPTIONAL {
+    ?cardinality a owl:Restriction .
     {
-      ?property a rdf:Property .
+      ?cardinality owl:cardinality ?card1 .
     } UNION {
-      ?property a owl:ObjectProperty .
+      ?cardinality owl:minCardinality ?card2 .
     } UNION {
-      ?property a owl:DatatypeProperty .
+      ?cardinality owl:maxCardinality ?card3 .
     }
-    FILTER(!STRSTARTS(STR(?property), "http://www.w3.org/"))
-  }
-  UNION
-  {
-    # Count Restrictions
-    ?restriction a owl:Restriction .
-    FILTER(!STRSTARTS(STR(?restriction), "http://www.w3.org/"))
   }
 }
\ No newline at end of file
diff --git a/queries/metrics/RF_006.rq b/queries/metrics/RF_006.rq
index 6a02413..2ba5613 100644
--- a/queries/metrics/RF_006.rq
+++ b/queries/metrics/RF_006.rq
@@ -1,37 +1,21 @@
+# This query counts the total number of distinct logical axioms in the dataset,
+# excluding built-in properties.
+
 PREFIX owl: <http://www.w3.org/2002/07/owl#>
-PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
 
 SELECT
-  (COUNT(DISTINCT ?restriction) as ?totalRestrictions)
-  (COUNT(DISTINCT ?someValues) as ?someValuesFrom)
-  (COUNT(DISTINCT ?allValues) as ?allValuesFrom)
-  (COUNT(DISTINCT ?hasValue) as ?hasValueRestrictions)
-  (COUNT(DISTINCT ?cardinality) as ?cardinalityRestrictions)
+    (COUNT(DISTINCT ?axiom) as ?totalLogicalAxioms)
 WHERE {
-  {
-    ?restriction a owl:Restriction .
-    FILTER(!STRSTARTS(STR(?restriction), "http://www.w3.org/"))
-  }
-  OPTIONAL {
-    ?someValues a owl:Restriction ;
-                owl:someValuesFrom ?target1 .
-  }
-  OPTIONAL {
-    ?allValues a owl:Restriction ;
-               owl:allValuesFrom ?target2 .
-  }
-  OPTIONAL {
-    ?hasValue a owl:Restriction ;
-              owl:hasValue ?target3 .
-  }
-  OPTIONAL {
-    ?cardinality a owl:Restriction .
-    {
-      ?cardinality owl:cardinality ?card1 .
-    } UNION {
-      ?cardinality owl:minCardinality ?card2 .
-    } UNION {
-      ?cardinality owl:maxCardinality ?card3 .
+    VALUES ?property {
+        owl:equivalentClass
+        owl:disjointWith
+        owl:complementOf
+        owl:intersectionOf
+        owl:unionOf
     }
-  }
+
+    ?axiom ?property ?target .
+
+    # Filter for built-in properties
+    FILTER(!STRSTARTS(STR(?axiom), "http://www.w3.org/"))
 }
\ No newline at end of file
diff --git a/queries/metrics/RF_007.rq b/queries/metrics/RF_007.rq
deleted file mode 100644
index 2ba5613..0000000
--- a/queries/metrics/RF_007.rq
+++ /dev/null
@@ -1,21 +0,0 @@
-# This query counts the total number of distinct logical axioms in the dataset,
-# excluding built-in properties.
-
-PREFIX owl: <http://www.w3.org/2002/07/owl#>
-
-SELECT
-    (COUNT(DISTINCT ?axiom) as ?totalLogicalAxioms)
-WHERE {
-    VALUES ?property {
-        owl:equivalentClass
-        owl:disjointWith
-        owl:complementOf
-        owl:intersectionOf
-        owl:unionOf
-    }
-
-    ?axiom ?property ?target .
-
-    # Filter for built-in properties
-    FILTER(!STRSTARTS(STR(?axiom), "http://www.w3.org/"))
-}
\ No newline at end of file
diff --git a/reports/metrics/complexity.json b/reports/metrics/complexity.json
new file mode 100644
index 0000000..f7b6070
--- /dev/null
+++ b/reports/metrics/complexity.json
@@ -0,0 +1,91 @@
+{
+    "structural_complexity": {
+        "name": "Structural Complexity",
+        "files": {
+            "classes": {
+                "name": "Number of Classes",
+                "file": "RF_001.rq",
+                "execute": true,
+                "weight": 0.2,
+                "result": "410",
+                "execution_time": 0.05,
+                "weighted_value": 82.0
+            },
+            "properties": {
+                "name": "Number of properties",
+                "file": "RF_002_1.rq",
+                "execute": true,
+                "weight": 0.2,
+                "result": "110",
+                "execution_time": 0.02,
+                "weighted_value": 22.0
+            },
+            "depth": {
+                "name": "Average class hierarchy depth",
+                "file": "RF_003.rq",
+                "execute": true,
+                "weight": 1,
+                "result": "3.34965034965035",
+                "execution_time": 0.02,
+                "weighted_value": 3.34965034965035
+            },
+            "width": {
+                "name": "Average class hierarchy width",
+                "file": "RF_004.rq",
+                "execute": true,
+                "weight": 1,
+                "result": "4.75",
+                "execution_time": 0.03,
+                "weighted_value": 4.75
+            }
+        }
+    },
+    "semantic_complexity": {
+        "name": "Semantic Complexity",
+        "files": {
+            "restrictions": {
+                "name": "Number of restrictions",
+                "file": "RF_005.rq",
+                "execute": true,
+                "weight": 0.2,
+                "result": "326",
+                "execution_time": 3.18,
+                "weighted_value": 65.2
+            },
+            "axioms": {
+                "name": "Number of logical axioms",
+                "file": "RF_006.rq",
+                "execute": true,
+                "weight": 1,
+                "result": "44",
+                "execution_time": 0.02,
+                "weighted_value": 44.0
+            }
+        }
+    },
+    "schematic_complexity": {
+        "name": "Schematic Complexity",
+        "files": {
+            "overall_complexity": {
+                "execute": false,
+                "name": "Overall Complexity",
+                "result": 221.29965034965034
+            },
+            "structural_complexity": {
+                "name": "Structural Complexity",
+                "execute": false,
+                "result": 112.09965034965035,
+                "weight": 1,
+                "weighted_value": 112.09965034965035
+            },
+            "semantic_complexity": {
+                "name": "Semantic Complexity",
+                "execute": false,
+                "result": 109.2,
+                "weight": 1,
+                "weighted_value": 109.2
+            }
+        }
+    },
+    "timestamp": "2025-03-19T10:40"
+}
\ No newline at end of file
-- 
GitLab


From b3ea949125b1da38a528f7855970d4684548aaa9 Mon Sep 17 00:00:00 2001
From: Ralf Klammer <ralf.klammer@tu-dresden.de>
Date: Wed, 19 Mar 2025 14:17:19 +0100
Subject: [PATCH 37/59] [metrics] forget *.pyc file

---
 docs/macros/__pycache__/main.cpython-312.pyc | Bin 5974 -> 7396 bytes
 1 file changed, 0 insertions(+), 0 deletions(-)

diff --git a/docs/macros/__pycache__/main.cpython-312.pyc b/docs/macros/__pycache__/main.cpython-312.pyc
index f5171ef96bc57fca46195c8c6b61980147820c96..c9a5d0a84bc4d87018d5c15c3c3186d023e43ba2 100644
GIT binary patch
delta 757
zcmcbn_r#L#G%qg~0|Ntt@r+yPWr7>|^cXqx7#SGaIi@pAViM=)Okr$cTn$pdz`)SO
zF^Nfxse@y36QepK*W^`<Rg;*+dD&YSU<zv4CNYT%av%w?S2AdFZ`NRX&(HXIvxHDN
z^X8NMdW?qhMX3e(MJ2`hxv3>ZnaRca$@#ejIjI$yC6#(v#rb(f><kPHx400Zla&OO
z7!@b4=M(2a2v(U+zQCt#h!99(U|=XNXJBAxVEDko%4vK@P;^Sl1!<ehVzxVqFAF+<
zU|`}kJy1B=fL~I4LCs}xlkcodyr$nlqMty-?=NhV6Zth|icA?87?K&G-Ud-@3=9my
z3=9mPB_==MSD&25%Rf1VE1YQ!+vMF`>Fn$^%(EF%I3{ay%ZGFN6<ISdFo1nfBoAUK
zfCw7~1_n*WB6AR18APan2vv|M3q({9B%lQ%v_alrVPIfTP*6}P0$F=YezF6PC~|n5
z<5QXJ$HQ)l@O(B%S1Z)>>|oE|5fHf`YIa$`e24IX@-NIxLVO<>Sa^LHC+qUdNH3^4
zz%`-%vbgtmPLSAlu*@egIXRBs#8ZJ`rlF$<^Fc07M+Jt1BCL+G><1;8L2OwtsUYdZ
z$$Zd~)rp1qpdF(VC-Wf|RwqtoO(s7-O-4UW##^kpiOEIznw*mtib~m9f;?smBJ4qg
z2*|OVP%nbK=>YL3S4wJHW?pK1YF=5969WSSH%Q&#$!|m_vVUOWWHtUEHaSF0k(-0n
z_%kyD8~;Zpj>!#TsWKp?D;bJpKt_YY>lcSjZhlH>PO4pz?_@@CAI?lhMq{uv0O&5c
AEC2ui

delta 233
zcmaE2c}<V+G%qg~0|Ntty5G%oQ@)LSdW;;;7#JAZ*`_m0ViM<PPho6fTn$pdz`)SO
zHi=1$se^5D6QepK$K+LvRV<YZnw*;pnBMa<7H>`vDrb(>Wb*UVWc1Tyyv3TEm|T>v
z$$pDDHLt8lnt_3#NC!k1fCv*1VF4mU7#J9e*%=rZ6u`itNSJ|v;TBg)YFcJqYCK4*
zH3I_!H%Qgw$zIYEnLjddPUe$Il>rH_WGIpW83NMti^C>2KczG$)vm~Ga=(lZXDB11
HF<2S^Sb;R4

-- 
GitLab


From 22665c5ae808c3a75b0746030e8a4cbee1e2f471 Mon Sep 17 00:00:00 2001
From: Ralf Klammer <ralf.klammer@tu-dresden.de>
Date: Wed, 19 Mar 2025 16:15:32 +0100
Subject: [PATCH 38/59] [metrics] forget *.pyc file

---
 docs/macros/__pycache__/main.cpython-312.pyc | Bin 7396 -> 0 bytes
 1 file changed, 0 insertions(+), 0 deletions(-)
 delete mode 100644 docs/macros/__pycache__/main.cpython-312.pyc

diff --git a/docs/macros/__pycache__/main.cpython-312.pyc b/docs/macros/__pycache__/main.cpython-312.pyc
deleted file mode 100644
index c9a5d0a84bc4d87018d5c15c3c3186d023e43ba2..0000000000000000000000000000000000000000
GIT binary patch
literal 0
HcmV?d00001

literal 7396
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
Y1<T6<R#zCTpEBrwX3Jz`GzQxO0Gjlbq5uE@

-- 
GitLab


From 9443349eb57d16f0942c4a8e72269fa15ddca596 Mon Sep 17 00:00:00 2001
From: Ralf Klammer <ralf.klammer@tu-dresden.de>
Date: Wed, 19 Mar 2025 16:16:16 +0100
Subject: [PATCH 39/59] [metrics] Enable setting reports-directory

---
 docs/macros/main.py                        |  60 +---
 docs/macros/table_renderer.py              | 174 +++++++----
 reports/metrics/complexity.json            |   6 +-
 reports/metrics/general.json               |  24 +-
 reports/metrics/resources.json             | 338 ++++++++++-----------
 scripts/kg_analysis/cli.py                 |  33 +-
 scripts/kg_analysis/interfaces/__init__.py |   5 +
 scripts/kg_analysis/metrics_runner.py      |  39 ++-
 8 files changed, 369 insertions(+), 310 deletions(-)

diff --git a/docs/macros/main.py b/docs/macros/main.py
index 4789dae..54bf058 100644
--- a/docs/macros/main.py
+++ b/docs/macros/main.py
@@ -7,6 +7,8 @@ sys.path.append(os.path.dirname(os.path.abspath(__file__)))
 
 from table_renderer import MetricsTableRenderer
 
+DEFAULT_REPORTS_DIR = Path(os.getenv("REPORTS_DIR", "./reports"))
+
 
 def define_env(env):
     @env.macro
@@ -54,63 +56,31 @@ def define_env(env):
 
     @env.macro
     def metrics_table_single_resource(resource_type=None):
-        try:
-            with open(
-                Path("reports/metrics/resources.json"), "r", encoding="utf-8"
-            ) as f:
-                renderer = MetricsTableRenderer(json.load(f))
-                return renderer.render("resource", resource_type)
-        except (FileNotFoundError, json.JSONDecodeError) as e:
-            return f"*Error loading metrics: {e}*"
+        return MetricsTableRenderer(
+            table_type="resource", resource_type=resource_type
+        ).render()
 
     @env.macro
     def metrics_table_overview_resource():
-        try:
-            with open(
-                Path("reports/metrics/resources.json"), "r", encoding="utf-8"
-            ) as f:
-                renderer = MetricsTableRenderer(json.load(f))
-                return renderer.render("resource_overview")
-        except (FileNotFoundError, json.JSONDecodeError) as e:
-            return f"*Error loading metrics: {e}*"
+        return MetricsTableRenderer(table_type="resource_overview").render()
 
     @env.macro
     def metrics_table_overview_general():
-        try:
-            with open(Path("reports/metrics/general.json"), "r") as f:
-                renderer = MetricsTableRenderer(json.load(f))
-                return renderer.render("general")
-        except (FileNotFoundError, json.JSONDecodeError) as e:
-            return f"*Error loading metrics: {e}*"
+        return MetricsTableRenderer(table_type="general").render()
 
     @env.macro
     def metrics_table_single_general(metric_key):
-        try:
-            with open(Path("reports/metrics/general.json"), "r") as f:
-                metrics = json.load(f)
-                if metric_key not in metrics:
-                    return f"*No data for metric: {metric_key}*"
-                renderer = MetricsTableRenderer({metric_key: metrics[metric_key]})
-                return renderer.render("general")
-        except (FileNotFoundError, json.JSONDecodeError) as e:
-            return f"*Error loading metrics: {e}*"
+        print(metric_key)
+        return MetricsTableRenderer(
+            table_type="general", metric_key=metric_key
+        ).render()
 
     @env.macro
     def metrics_table_overview_complexity():
-        try:
-            with open(Path("reports/metrics/complexity.json"), "r") as f:
-                renderer = MetricsTableRenderer(json.load(f))
-                return renderer.render("complexity")
-        except (FileNotFoundError, json.JSONDecodeError) as e:
-            return f"*Error loading metrics: {e}*"
+        return MetricsTableRenderer(table_type="complexity").render()
 
     @env.macro
     def metrics_table_single_complexity(metric_key):
-        try:
-            with open(
-                Path("reports/metrics/complexity.json"), "r", encoding="utf-8"
-            ) as f:
-                renderer = MetricsTableRenderer(json.load(f))
-                return renderer.render("complexity", metric_key=metric_key)
-        except (FileNotFoundError, json.JSONDecodeError) as e:
-            return f"*Error loading metrics: {e}*"
+        return MetricsTableRenderer(
+            table_type="complexity", metric_key=metric_key
+        ).render()
diff --git a/docs/macros/table_renderer.py b/docs/macros/table_renderer.py
index a330a20..b7d19bc 100644
--- a/docs/macros/table_renderer.py
+++ b/docs/macros/table_renderer.py
@@ -1,18 +1,82 @@
+import json
+import os
+
+from pathlib import Path
+
+REPORTS_DIR = Path(os.getenv("REPORTS_DIR", "./reports/metrics"))
+
+
 class MetricsTableRenderer:
     """Class for rendering metric tables in various formats."""
 
-    def __init__(self, data):
+    def __init__(
+        self,
+        data=None,
+        table_type="general",
+        resource_type=None,
+        metric_key=None,
+    ):
         """
         Initializes the renderer with metric data.
 
         Args:
             data (dict): The JSON data containing the metrics
         """
-        self.data = data
-        self.timestamp = data.get("timestamp", "unknown")
+        self._data = data
+        self.table_type = table_type
+        self.resource_type = resource_type
+        self.metric_key = metric_key
         self.output = []
 
-    def render(self, table_type="general", resource_type=None, metric_key=None):
+    def open_file(self):
+        """Opens the file and returns the content."""
+        try:
+            with open(
+                REPORTS_DIR.joinpath(self.table_option["report"]),
+                "r",
+                encoding="utf-8",
+            ) as f:
+                return json.load(f)
+        except (FileNotFoundError, json.JSONDecodeError) as e:
+            raise f"*Error loading metrics: {e}*"
+
+    @property
+    def data(self):
+        if not self._data:
+            self._data = self.open_file()
+        return self._data
+
+    @property
+    def timestamp(self):
+        return self.data.get("timestamp", "unknown")
+
+    @property
+    def table_option(self):
+        table_options = {
+            "resource_overview": {
+                "method": self._render_resource_overview,
+                "report": "resources.json",
+            },
+            "resource": {
+                "method": lambda: self._render_resource(self.resource_type),
+                "report": "resources.json",
+            },
+            # "general_overview": {
+            #     "method": self._render_general,
+            #     "report": "general.json",
+            # },
+            "general": {
+                "method": self._render_general,
+                "report": "general.json",
+            },
+            "complexity": {
+                "method": self._render_complexity,
+                "report": "complexity.json",
+            },
+        }
+        return table_options[self.table_type]
+
+    def render(self):
         """
         Renders the table in the desired format.
 
@@ -26,17 +90,9 @@ class MetricsTableRenderer:
         self.output = []
 
         # Dictionary mapping table types to their corresponding rendering methods
-        render_methods = {
-            "resource_overview": self._render_resource_overview,
-            "resource": lambda: self._render_resource(resource_type),
-            "general_overview": self._render_general_overview,
-            "general": self._render_general,
-            "complexity": lambda: self._render_complexity(metric_key),
-        }
 
         # Get the appropriate rendering method based on the table type
-        render_method = render_methods.get(table_type, self._render_general)
-        render_method()
+        self.table_option["method"]()
 
         # Append the timestamp to the output
         self.output.append(f"\n*Last updated: {self.timestamp}*\n")
@@ -97,34 +153,34 @@ class MetricsTableRenderer:
                         f"| {sub_query_data['name']} | [{sub_query_data['file']}](#{sub_query_data['file']}) | {sub_query_data['result']} |"
                     )
 
-    def _render_general_overview(self):
-        """Renders the general overview table."""
-        # Get the sorted list of resource types
-        resource_types = sorted([rt for rt in self.data.keys() if rt != "timestamp"])
-        metric_names = []
+    # def _render_general_overview(self):
+    #     """Renders the general overview table."""
+    #     # Get the sorted list of resource types
+    #     resource_types = sorted([rt for rt in self.data.keys() if rt != "timestamp"])
+    #     metric_names = []
 
-        # Collect metric names from the first resource type
-        first_rt = self.data[resource_types[0]]
-        for query in first_rt.get("queries", {}).values():
-            if "name" in query:
-                metric_names.append(query["name"])
-
-        # Construct the table header
-        self.output.append("| Resource Type | " + " | ".join(metric_names) + " |")
-        self.output.append("|" + "|".join(["---"] * (len(metric_names) + 1)) + "|")
-
-        # Iterate over each resource type to collect results
-        for rt in resource_types:
-            row = [rt]
-            queries = self.data[rt].get("queries", {})
-            for metric in metric_names:
-                result = "-"
-                for q in queries.values():
-                    if q.get("name") == metric:
-                        result = str(q.get("result", "-"))
-                        break
-                row.append(result)
-            self.output.append("| " + " | ".join(row) + " |")
+    #     # Collect metric names from the first resource type
+    #     first_rt = self.data[resource_types[0]]
+    #     for query in first_rt.get("queries", {}).values():
+    #         if "name" in query:
+    #             metric_names.append(query["name"])
+
+    #     # Construct the table header
+    #     self.output.append("| Resource Type | " + " | ".join(metric_names) + " |")
+    #     self.output.append("|" + "|".join(["---"] * (len(metric_names) + 1)) + "|")
+
+    #     # Iterate over each resource type to collect results
+    #     for rt in resource_types:
+    #         row = [rt]
+    #         queries = self.data[rt].get("queries", {})
+    #         for metric in metric_names:
+    #             result = "-"
+    #             for q in queries.values():
+    #                 if q.get("name") == metric:
+    #                     result = str(q.get("result", "-"))
+    #                     break
+    #             row.append(result)
+    #         self.output.append("| " + " | ".join(row) + " |")
 
     def _render_general(self):
         """Renders the general table."""
@@ -132,7 +188,12 @@ class MetricsTableRenderer:
         self.output.append("|--------|-------|--------|")
 
         # Iterate over each metric data to construct the table rows
-        for metric_key, metric_data in self.data.items():
+        data = (
+            {self.metric_key: self.data[self.metric_key]}
+            if self.metric_key
+            else self.data
+        )
+        for metric_key, metric_data in data.items():
             if metric_key == "timestamp":
                 continue
             if isinstance(metric_data, dict):
@@ -147,18 +208,15 @@ class MetricsTableRenderer:
                         f"| {metric_data['name']} | {metric_data['file']} | {metric_data.get('result', '-')} |"
                     )
 
-    def _render_complexity(self, cmplxty_type=None):
-        """Renders a complexity table.
-
-        Args:
-            cmplxty_type (str, optional): Specific complexity type.
-                If None, an overview of all types is created.
-        """
+    def _render_complexity(self):
+        """Renders a complexity table."""
         # Base header for all tables
         header = ["Metric", "Query", "Value", "Weight", "Weighted Value"]
 
+        metric_type = self.metric_key
+
         # Add Category column for overview
-        if not cmplxty_type:
+        if not metric_type:
             header += ["Category"]
 
         # Create table header
@@ -167,21 +225,21 @@ class MetricsTableRenderer:
 
         # Select categories based on type
         categories = {}
-        if cmplxty_type:
+        if metric_type:
             # Search directly or in nested structures
-            if cmplxty_type in self.data:
-                categories[cmplxty_type] = self.data[cmplxty_type]
+            if metric_type in self.data:
+                categories[metric_type] = self.data[metric_type]
             else:
                 for value in self.data.values():
                     if (
                         isinstance(value, dict)
                         and "files" in value
-                        and cmplxty_type in value["files"]
+                        and metric_type in value["files"]
                     ):
-                        categories[cmplxty_type] = {
-                            "name": cmplxty_type,
+                        categories[metric_type] = {
+                            "name": metric_type,
                             "files": {
-                                cmplxty_type: value["files"][cmplxty_type],
+                                metric_type: value["files"][metric_type],
                             },
                         }
                         break
@@ -196,7 +254,7 @@ class MetricsTableRenderer:
 
             # Category header with appropriate number of empty columns
             empty_cols = len(header) - 1
-            if not cmplxty_type:
+            if not metric_type:
                 self.output.append(
                     f"| **{data['name']}** | " + " | " * empty_cols + "|"
                 )
@@ -211,7 +269,7 @@ class MetricsTableRenderer:
                 ]
 
                 # Add Category column for overview
-                if not cmplxty_type:
+                if not metric_type:
                     row.insert(0, "")
 
                 self.output.append("| " + " | ".join(row) + " |")
diff --git a/reports/metrics/complexity.json b/reports/metrics/complexity.json
index f7b6070..aee2df8 100644
--- a/reports/metrics/complexity.json
+++ b/reports/metrics/complexity.json
@@ -26,7 +26,7 @@
                 "execute": true,
                 "weight": 1,
                 "result": "3.34965034965035",
-                "execution_time": 0.02,
+                "execution_time": 0.03,
                 "weighted_value": 3.34965034965035
             },
             "width": {
@@ -49,7 +49,7 @@
                 "execute": true,
                 "weight": 0.2,
                 "result": "326",
-                "execution_time": 3.18,
+                "execution_time": 0.02,
                 "weighted_value": 65.2
             },
             "axioms": {
@@ -87,5 +87,5 @@
             }
         }
     },
-    "timestamp": "2025-03-19T10:40"
+    "timestamp": "2025-03-19T14:41"
 }
\ No newline at end of file
diff --git a/reports/metrics/general.json b/reports/metrics/general.json
index 511a23d..1128352 100644
--- a/reports/metrics/general.json
+++ b/reports/metrics/general.json
@@ -4,21 +4,21 @@
         "file": "GM001.rq",
         "execute": true,
         "result": "1252089",
-        "execution_time": 1.06
+        "execution_time": 0.43
     },
     "assertions": {
         "name": "Assertions Count",
         "file": "GM002_1.rq",
         "execute": true,
         "result": "40786353",
-        "execution_time": 0.01
+        "execution_time": 0.24
     },
     "linkage": {
         "name": "Average Linkage Degree",
         "file": "GM003_2.rq",
         "execute": true,
         "result": "3.24508957824795",
-        "execution_time": 0.01
+        "execution_time": 23.04
     },
     "edges_out": {
         "name": "Edges - outgoing",
@@ -28,14 +28,14 @@
                 "file": "GM004_2.rq",
                 "execute": true,
                 "result": "6705836",
-                "execution_time": 0.01
+                "execution_time": 13.4
             },
             "min": {
                 "name": "Minimum of outgoing edges",
                 "file": "GM004_5.rq",
                 "execute": true,
                 "result": "1",
-                "execution_time": 0.01
+                "execution_time": 23.76
             },
             "median": {
                 "name": "Median of outgoing edges",
@@ -45,14 +45,14 @@
                 },
                 "execute": true,
                 "result": "2",
-                "execution_time": 0.01
+                "execution_time": 31.02
             },
             "max": {
                 "name": "Maximum of outgoing edges",
                 "file": "GM004_6.rq",
                 "execute": true,
                 "result": "282499",
-                "execution_time": 0.01
+                "execution_time": 24.42
             }
         }
     },
@@ -64,14 +64,14 @@
                 "file": "GM005_2.rq",
                 "execute": true,
                 "result": "15111836",
-                "execution_time": 24.99
+                "execution_time": 25.74
             },
             "min": {
                 "name": "Minimum of incoming edges",
                 "file": "GM005_5.rq",
                 "execute": true,
                 "result": "1",
-                "execution_time": 21.37
+                "execution_time": 21.76
             },
             "median": {
                 "name": "Median of incoming edges",
@@ -81,16 +81,16 @@
                 },
                 "execute": true,
                 "result": "1",
-                "execution_time": 35.6
+                "execution_time": 36.21
             },
             "max": {
                 "name": "Maximum of incoming edges",
                 "file": "GM005_6.rq",
                 "execute": true,
                 "result": "1121975",
-                "execution_time": 22.57
+                "execution_time": 22.61
             }
         }
     },
-    "timestamp": "2025-03-17T15:15"
+    "timestamp": "2025-03-18T10:45"
 }
\ No newline at end of file
diff --git a/reports/metrics/resources.json b/reports/metrics/resources.json
index 9e34292..3015d03 100644
--- a/reports/metrics/resources.json
+++ b/reports/metrics/resources.json
@@ -5,21 +5,21 @@
             "file": "RM001_instances_template.rq",
             "execute": true,
             "result": "724903",
-            "execution_time": 0.01
+            "execution_time": 0.42
         },
         "assertions": {
             "name": "Number of Assertions",
             "file": "RM002_assertions_template.rq",
             "execute": true,
             "result": "724904",
-            "execution_time": 0.01
+            "execution_time": 0.28
         },
         "linkage": {
             "name": "Average Linkage",
             "file": "RM003_linkage_template.rq",
             "execute": true,
             "result": "68",
-            "execution_time": 0.01
+            "execution_time": 0.22
         },
         "edges_out": {
             "name": "Edges - outgoing",
@@ -29,14 +29,14 @@
                     "file": "RM004_1_out_edges_total_template.rq",
                     "execute": true,
                     "result": "724903",
-                    "execution_time": 0.01
+                    "execution_time": 4.67
                 },
                 "min": {
                     "name": "Minimum of outgoing edges",
                     "file": "RM004_2_out_edges_min_template.rq",
                     "execute": true,
                     "result": "6",
-                    "execution_time": 0.01
+                    "execution_time": 0.83
                 },
                 "median": {
                     "name": "Median of outgoing edges",
@@ -46,14 +46,14 @@
                     },
                     "execute": true,
                     "result": "23",
-                    "execution_time": 0.01
+                    "execution_time": 1.5
                 },
                 "max": {
                     "name": "Maximum of outgoing edges",
                     "file": "RM004_4_out_edges_max_template.rq",
                     "execute": true,
                     "result": "12519",
-                    "execution_time": 0.01
+                    "execution_time": 1.26
                 }
             }
         },
@@ -65,14 +65,14 @@
                     "file": "RM005_1_in_edges_total_template.rq",
                     "execute": true,
                     "result": "4",
-                    "execution_time": 0.01
+                    "execution_time": 1.32
                 },
                 "min": {
                     "name": "Minimum of incoming edges",
                     "file": "RM005_2_in_edges_min_template.rq",
                     "execute": true,
                     "result": "1",
-                    "execution_time": 0.01
+                    "execution_time": 0.22
                 },
                 "median": {
                     "name": "Median of incoming edges",
@@ -82,14 +82,14 @@
                     },
                     "execute": true,
                     "result": "1",
-                    "execution_time": 0.01
+                    "execution_time": 0.22
                 },
                 "max": {
                     "name": "Maximum of incoming edges",
                     "file": "RM005_4_in_edges_max_template.rq",
                     "execute": true,
                     "result": "107",
-                    "execution_time": 0.01
+                    "execution_time": 0.22
                 }
             }
         },
@@ -98,10 +98,10 @@
             "file": "RM006_connectivity_template.rq",
             "execute": true,
             "result": "427594",
-            "execution_time": 0.01
+            "execution_time": 4.48
         },
-        "timestamp": "2025-03-17T15:15",
-        "file": "06_dataset.md"
+        "timestamp": "2025-03-18T10:45",
+        "file": "dataset.md"
     },
     "publication": {
         "instances": {
@@ -109,21 +109,21 @@
             "file": "RM001_instances_template.rq",
             "execute": true,
             "result": "5",
-            "execution_time": 0.01
+            "execution_time": 0.22
         },
         "assertions": {
             "name": "Number of Assertions",
             "file": "RM002_assertions_template.rq",
             "execute": true,
             "result": "6",
-            "execution_time": 0.01
+            "execution_time": 0.22
         },
         "linkage": {
             "name": "Average Linkage",
             "file": "RM003_linkage_template.rq",
             "execute": true,
             "result": 0,
-            "execution_time": 0.01
+            "execution_time": 0.19
         },
         "edges_out": {
             "name": "Edges - outgoing",
@@ -133,14 +133,14 @@
                     "file": "RM004_1_out_edges_total_template.rq",
                     "execute": true,
                     "result": "5",
-                    "execution_time": 0.01
+                    "execution_time": 0.19
                 },
                 "min": {
                     "name": "Minimum of outgoing edges",
                     "file": "RM004_2_out_edges_min_template.rq",
                     "execute": true,
                     "result": "7",
-                    "execution_time": 0.01
+                    "execution_time": 0.22
                 },
                 "median": {
                     "name": "Median of outgoing edges",
@@ -150,14 +150,14 @@
                     },
                     "execute": true,
                     "result": "9",
-                    "execution_time": 0.01
+                    "execution_time": 0.2
                 },
                 "max": {
                     "name": "Maximum of outgoing edges",
                     "file": "RM004_4_out_edges_max_template.rq",
                     "execute": true,
                     "result": "11",
-                    "execution_time": 0.01
+                    "execution_time": 0.22
                 }
             }
         },
@@ -169,14 +169,14 @@
                     "file": "RM005_1_in_edges_total_template.rq",
                     "execute": true,
                     "result": "0",
-                    "execution_time": 0.01
+                    "execution_time": 0.22
                 },
                 "min": {
                     "name": "Minimum of incoming edges",
                     "file": "RM005_2_in_edges_min_template.rq",
                     "execute": true,
                     "result": null,
-                    "execution_time": 0.01
+                    "execution_time": 0.22
                 },
                 "median": {
                     "name": "Median of incoming edges",
@@ -186,14 +186,14 @@
                     },
                     "execute": true,
                     "result": null,
-                    "execution_time": 0.01
+                    "execution_time": 0.2
                 },
                 "max": {
                     "name": "Maximum of incoming edges",
                     "file": "RM005_4_in_edges_max_template.rq",
                     "execute": true,
                     "result": null,
-                    "execution_time": 0.01
+                    "execution_time": 0.22
                 }
             }
         },
@@ -202,10 +202,10 @@
             "file": "RM006_connectivity_template.rq",
             "execute": true,
             "result": "15",
-            "execution_time": 0.01
+            "execution_time": 0.22
         },
-        "timestamp": "2025-03-17T15:15",
-        "file": "07_publication.md"
+        "timestamp": "2025-03-18T10:45",
+        "file": "publication.md"
     },
     "learning_resource": {
         "instances": {
@@ -213,21 +213,21 @@
             "file": "RM001_instances_template.rq",
             "execute": true,
             "result": "512",
-            "execution_time": 0.01
+            "execution_time": 0.21
         },
         "assertions": {
             "name": "Number of Assertions",
             "file": "RM002_assertions_template.rq",
             "execute": true,
             "result": "513",
-            "execution_time": 0.01
+            "execution_time": 0.19
         },
         "linkage": {
             "name": "Average Linkage",
             "file": "RM003_linkage_template.rq",
             "execute": true,
             "result": "25",
-            "execution_time": 0.01
+            "execution_time": 0.2
         },
         "edges_out": {
             "name": "Edges - outgoing",
@@ -237,14 +237,14 @@
                     "file": "RM004_1_out_edges_total_template.rq",
                     "execute": true,
                     "result": "512",
-                    "execution_time": 0.01
+                    "execution_time": 0.19
                 },
                 "min": {
                     "name": "Minimum of outgoing edges",
                     "file": "RM004_2_out_edges_min_template.rq",
                     "execute": true,
                     "result": "10",
-                    "execution_time": 0.01
+                    "execution_time": 0.22
                 },
                 "median": {
                     "name": "Median of outgoing edges",
@@ -254,14 +254,14 @@
                     },
                     "execute": true,
                     "result": "15",
-                    "execution_time": 0.01
+                    "execution_time": 0.21
                 },
                 "max": {
                     "name": "Maximum of outgoing edges",
                     "file": "RM004_4_out_edges_max_template.rq",
                     "execute": true,
                     "result": "27",
-                    "execution_time": 0.01
+                    "execution_time": 0.2
                 }
             }
         },
@@ -273,14 +273,14 @@
                     "file": "RM005_1_in_edges_total_template.rq",
                     "execute": true,
                     "result": "1",
-                    "execution_time": 0.01
+                    "execution_time": 0.21
                 },
                 "min": {
                     "name": "Minimum of incoming edges",
                     "file": "RM005_2_in_edges_min_template.rq",
                     "execute": true,
                     "result": "3",
-                    "execution_time": 1.02
+                    "execution_time": 0.2
                 },
                 "median": {
                     "name": "Median of incoming edges",
@@ -290,14 +290,14 @@
                     },
                     "execute": true,
                     "result": "3",
-                    "execution_time": 0.01
+                    "execution_time": 0.1
                 },
                 "max": {
                     "name": "Maximum of incoming edges",
                     "file": "RM005_4_in_edges_max_template.rq",
                     "execute": true,
                     "result": "3",
-                    "execution_time": 0.01
+                    "execution_time": 0.19
                 }
             }
         },
@@ -306,10 +306,10 @@
             "file": "RM006_connectivity_template.rq",
             "execute": true,
             "result": "530",
-            "execution_time": 0.01
+            "execution_time": 0.19
         },
-        "timestamp": "2025-03-17T15:15",
-        "file": "08_learning_resource.md"
+        "timestamp": "2025-03-18T10:46",
+        "file": "learning_resource.md"
     },
     "repository": {
         "instances": {
@@ -317,21 +317,21 @@
             "file": "RM001_instances_template.rq",
             "execute": true,
             "result": "159",
-            "execution_time": 0.01
+            "execution_time": 0.19
         },
         "assertions": {
             "name": "Number of Assertions",
             "file": "RM002_assertions_template.rq",
             "execute": true,
             "result": "162",
-            "execution_time": 0.01
+            "execution_time": 0.19
         },
         "linkage": {
             "name": "Average Linkage",
             "file": "RM003_linkage_template.rq",
             "execute": true,
             "result": "90212",
-            "execution_time": 1.03
+            "execution_time": 0.22
         },
         "edges_out": {
             "name": "Edges - outgoing",
@@ -341,14 +341,14 @@
                     "file": "RM004_1_out_edges_total_template.rq",
                     "execute": true,
                     "result": "159",
-                    "execution_time": 0.01
+                    "execution_time": 0.1
                 },
                 "min": {
                     "name": "Minimum of outgoing edges",
                     "file": "RM004_2_out_edges_min_template.rq",
                     "execute": true,
                     "result": "7",
-                    "execution_time": 0.01
+                    "execution_time": 0.17
                 },
                 "median": {
                     "name": "Median of outgoing edges",
@@ -358,14 +358,14 @@
                     },
                     "execute": true,
                     "result": "44",
-                    "execution_time": 0.01
+                    "execution_time": 0.21
                 },
                 "max": {
                     "name": "Maximum of outgoing edges",
                     "file": "RM004_4_out_edges_max_template.rq",
                     "execute": true,
                     "result": "92",
-                    "execution_time": 0.01
+                    "execution_time": 0.22
                 }
             }
         },
@@ -377,14 +377,14 @@
                     "file": "RM005_1_in_edges_total_template.rq",
                     "execute": true,
                     "result": "5",
-                    "execution_time": 0.01
+                    "execution_time": 0.22
                 },
                 "min": {
                     "name": "Minimum of incoming edges",
                     "file": "RM005_2_in_edges_min_template.rq",
                     "execute": true,
                     "result": "1417",
-                    "execution_time": 0.01
+                    "execution_time": 0.22
                 },
                 "median": {
                     "name": "Median of incoming edges",
@@ -394,14 +394,14 @@
                     },
                     "execute": true,
                     "result": "6718",
-                    "execution_time": 0.01
+                    "execution_time": 0.22
                 },
                 "max": {
                     "name": "Maximum of incoming edges",
                     "file": "RM005_4_in_edges_max_template.rq",
                     "execute": true,
                     "result": "432941",
-                    "execution_time": 0.01
+                    "execution_time": 0.22
                 }
             }
         },
@@ -410,10 +410,10 @@
             "file": "RM006_connectivity_template.rq",
             "execute": true,
             "result": "825",
-            "execution_time": 0.01
+            "execution_time": 0.2
         },
-        "timestamp": "2025-03-17T15:15",
-        "file": "09_repository.md"
+        "timestamp": "2025-03-18T10:46",
+        "file": "repository.md"
     },
     "article_lhb": {
         "instances": {
@@ -421,21 +421,21 @@
             "file": "RM001_instances_template.rq",
             "execute": true,
             "result": "114",
-            "execution_time": 0.01
+            "execution_time": 0.22
         },
         "assertions": {
             "name": "Number of Assertions",
             "file": "RM002_assertions_template.rq",
             "execute": true,
             "result": "117",
-            "execution_time": 0.01
+            "execution_time": 0.21
         },
         "linkage": {
             "name": "Average Linkage",
             "file": "RM003_linkage_template.rq",
             "execute": true,
             "result": "33.245098039215686",
-            "execution_time": 0.01
+            "execution_time": 0.21
         },
         "edges_out": {
             "name": "Edges - outgoing",
@@ -445,14 +445,14 @@
                     "file": "RM004_1_out_edges_total_template.rq",
                     "execute": true,
                     "result": "114",
-                    "execution_time": 0.01
+                    "execution_time": 0.21
                 },
                 "min": {
                     "name": "Minimum of outgoing edges",
                     "file": "RM004_2_out_edges_min_template.rq",
                     "execute": true,
                     "result": "11",
-                    "execution_time": 0.01
+                    "execution_time": 0.2
                 },
                 "median": {
                     "name": "Median of outgoing edges",
@@ -462,14 +462,14 @@
                     },
                     "execute": true,
                     "result": "30",
-                    "execution_time": 0.01
+                    "execution_time": 0.19
                 },
                 "max": {
                     "name": "Maximum of outgoing edges",
                     "file": "RM004_4_out_edges_max_template.rq",
                     "execute": true,
                     "result": "62",
-                    "execution_time": 0.01
+                    "execution_time": 0.22
                 }
             }
         },
@@ -481,14 +481,14 @@
                     "file": "RM005_1_in_edges_total_template.rq",
                     "execute": true,
                     "result": "102",
-                    "execution_time": 0.01
+                    "execution_time": 0.14
                 },
                 "min": {
                     "name": "Minimum of incoming edges",
                     "file": "RM005_2_in_edges_min_template.rq",
                     "execute": true,
                     "result": "1",
-                    "execution_time": 0.01
+                    "execution_time": 0.15
                 },
                 "median": {
                     "name": "Median of incoming edges",
@@ -498,14 +498,14 @@
                     },
                     "execute": true,
                     "result": "2",
-                    "execution_time": 0.01
+                    "execution_time": 0.2
                 },
                 "max": {
                     "name": "Maximum of incoming edges",
                     "file": "RM005_4_in_edges_max_template.rq",
                     "execute": true,
                     "result": "24",
-                    "execution_time": 0.01
+                    "execution_time": 0.22
                 }
             }
         },
@@ -514,10 +514,10 @@
             "file": "RM006_connectivity_template.rq",
             "execute": true,
             "result": "592",
-            "execution_time": 0.01
+            "execution_time": 0.22
         },
-        "timestamp": "2025-03-17T15:15",
-        "file": "10_article_lhb.md"
+        "timestamp": "2025-03-18T10:46",
+        "file": "article_lhb.md"
     },
     "standards": {
         "instances": {
@@ -525,21 +525,21 @@
             "file": "RM001_instances_template.rq",
             "execute": true,
             "result": "89",
-            "execution_time": 0.01
+            "execution_time": 0.22
         },
         "assertions": {
             "name": "Number of Assertions",
             "file": "RM002_assertions_template.rq",
             "execute": true,
             "result": "101",
-            "execution_time": 0.01
+            "execution_time": 0.13
         },
         "linkage": {
             "name": "Average Linkage",
             "file": "RM003_linkage_template.rq",
             "execute": true,
             "result": "13.714285714285714",
-            "execution_time": 1.02
+            "execution_time": 0.15
         },
         "edges_out": {
             "name": "Edges - outgoing",
@@ -549,14 +549,14 @@
                     "file": "RM004_1_out_edges_total_template.rq",
                     "execute": true,
                     "result": "89",
-                    "execution_time": 0.01
+                    "execution_time": 0.21
                 },
                 "min": {
                     "name": "Minimum of outgoing edges",
                     "file": "RM004_2_out_edges_min_template.rq",
                     "execute": true,
                     "result": "5",
-                    "execution_time": 0.01
+                    "execution_time": 0.19
                 },
                 "median": {
                     "name": "Median of outgoing edges",
@@ -566,14 +566,14 @@
                     },
                     "execute": true,
                     "result": "9",
-                    "execution_time": 0.01
+                    "execution_time": 0.2
                 },
                 "max": {
                     "name": "Maximum of outgoing edges",
                     "file": "RM004_4_out_edges_max_template.rq",
                     "execute": true,
                     "result": "27",
-                    "execution_time": 0.01
+                    "execution_time": 0.1
                 }
             }
         },
@@ -585,14 +585,14 @@
                     "file": "RM005_1_in_edges_total_template.rq",
                     "execute": true,
                     "result": "77",
-                    "execution_time": 0.01
+                    "execution_time": 0.19
                 },
                 "min": {
                     "name": "Minimum of incoming edges",
                     "file": "RM005_2_in_edges_min_template.rq",
                     "execute": true,
                     "result": "1",
-                    "execution_time": 0.01
+                    "execution_time": 0.2
                 },
                 "median": {
                     "name": "Median of incoming edges",
@@ -602,14 +602,14 @@
                     },
                     "execute": true,
                     "result": "1",
-                    "execution_time": 0.01
+                    "execution_time": 0.19
                 },
                 "max": {
                     "name": "Maximum of incoming edges",
                     "file": "RM005_4_in_edges_max_template.rq",
                     "execute": true,
                     "result": "56",
-                    "execution_time": 0.01
+                    "execution_time": 0.22
                 }
             }
         },
@@ -618,10 +618,10 @@
             "file": "RM006_connectivity_template.rq",
             "execute": true,
             "result": "100",
-            "execution_time": 0.01
+            "execution_time": 1.2
         },
-        "timestamp": "2025-03-17T15:15",
-        "file": "11_standards.md"
+        "timestamp": "2025-03-18T10:46",
+        "file": "standards.md"
     },
     "software": {
         "instances": {
@@ -629,21 +629,21 @@
             "file": "RM001_instances_template.rq",
             "execute": true,
             "result": "147",
-            "execution_time": 0.01
+            "execution_time": 0.2
         },
         "assertions": {
             "name": "Number of Assertions",
             "file": "RM002_assertions_template.rq",
             "execute": true,
             "result": "148",
-            "execution_time": 0.01
+            "execution_time": 0.22
         },
         "linkage": {
             "name": "Average Linkage",
             "file": "RM003_linkage_template.rq",
             "execute": true,
             "result": "11.5",
-            "execution_time": 0.01
+            "execution_time": 0.23
         },
         "edges_out": {
             "name": "Edges - outgoing",
@@ -653,14 +653,14 @@
                     "file": "RM004_1_out_edges_total_template.rq",
                     "execute": true,
                     "result": "147",
-                    "execution_time": 0.01
+                    "execution_time": 0.21
                 },
                 "min": {
                     "name": "Minimum of outgoing edges",
                     "file": "RM004_2_out_edges_min_template.rq",
                     "execute": true,
                     "result": "7",
-                    "execution_time": 0.01
+                    "execution_time": 0.17
                 },
                 "median": {
                     "name": "Median of outgoing edges",
@@ -670,14 +670,14 @@
                     },
                     "execute": true,
                     "result": "20",
-                    "execution_time": 0.01
+                    "execution_time": 0.22
                 },
                 "max": {
                     "name": "Maximum of outgoing edges",
                     "file": "RM004_4_out_edges_max_template.rq",
                     "execute": true,
                     "result": "122",
-                    "execution_time": 0.01
+                    "execution_time": 0.22
                 }
             }
         },
@@ -689,14 +689,14 @@
                     "file": "RM005_1_in_edges_total_template.rq",
                     "execute": true,
                     "result": "8",
-                    "execution_time": 0.01
+                    "execution_time": 0.22
                 },
                 "min": {
                     "name": "Minimum of incoming edges",
                     "file": "RM005_2_in_edges_min_template.rq",
                     "execute": true,
                     "result": "1",
-                    "execution_time": 0.01
+                    "execution_time": 0.1
                 },
                 "median": {
                     "name": "Median of incoming edges",
@@ -706,14 +706,14 @@
                     },
                     "execute": true,
                     "result": "1",
-                    "execution_time": 0.01
+                    "execution_time": 0.17
                 },
                 "max": {
                     "name": "Maximum of incoming edges",
                     "file": "RM005_4_in_edges_max_template.rq",
                     "execute": true,
                     "result": "2",
-                    "execution_time": 0.01
+                    "execution_time": 0.19
                 }
             }
         },
@@ -722,10 +722,10 @@
             "file": "RM006_connectivity_template.rq",
             "execute": true,
             "result": "1103",
-            "execution_time": 0.01
+            "execution_time": 0.22
         },
-        "timestamp": "2025-03-17T15:15",
-        "file": "13_software.md"
+        "timestamp": "2025-03-18T10:46",
+        "file": "software.md"
     },
     "service": {
         "instances": {
@@ -733,21 +733,21 @@
             "file": "RM001_instances_template.rq",
             "execute": true,
             "result": "1",
-            "execution_time": 0.01
+            "execution_time": 0.24
         },
         "assertions": {
             "name": "Number of Assertions",
             "file": "RM002_assertions_template.rq",
             "execute": true,
             "result": "1",
-            "execution_time": 0.01
+            "execution_time": 0.19
         },
         "linkage": {
             "name": "Average Linkage",
             "file": "RM003_linkage_template.rq",
             "execute": true,
             "result": 0,
-            "execution_time": 0.01
+            "execution_time": 0.22
         },
         "edges_out": {
             "name": "Edges - outgoing",
@@ -757,14 +757,14 @@
                     "file": "RM004_1_out_edges_total_template.rq",
                     "execute": true,
                     "result": "1",
-                    "execution_time": 0.01
+                    "execution_time": 0.1
                 },
                 "min": {
                     "name": "Minimum of outgoing edges",
                     "file": "RM004_2_out_edges_min_template.rq",
                     "execute": true,
                     "result": "14",
-                    "execution_time": 0.01
+                    "execution_time": 0.2
                 },
                 "median": {
                     "name": "Median of outgoing edges",
@@ -774,14 +774,14 @@
                     },
                     "execute": true,
                     "result": "14",
-                    "execution_time": 0.01
+                    "execution_time": 0.19
                 },
                 "max": {
                     "name": "Maximum of outgoing edges",
                     "file": "RM004_4_out_edges_max_template.rq",
                     "execute": true,
                     "result": "14",
-                    "execution_time": 0.01
+                    "execution_time": 0.16
                 }
             }
         },
@@ -793,14 +793,14 @@
                     "file": "RM005_1_in_edges_total_template.rq",
                     "execute": true,
                     "result": "0",
-                    "execution_time": 0.01
+                    "execution_time": 0.12
                 },
                 "min": {
                     "name": "Minimum of incoming edges",
                     "file": "RM005_2_in_edges_min_template.rq",
                     "execute": true,
                     "result": null,
-                    "execution_time": 0.01
+                    "execution_time": 0.22
                 },
                 "median": {
                     "name": "Median of incoming edges",
@@ -810,14 +810,14 @@
                     },
                     "execute": true,
                     "result": null,
-                    "execution_time": 0.01
+                    "execution_time": 0.2
                 },
                 "max": {
                     "name": "Maximum of incoming edges",
                     "file": "RM005_4_in_edges_max_template.rq",
                     "execute": true,
                     "result": null,
-                    "execution_time": 0.01
+                    "execution_time": 0.19
                 }
             }
         },
@@ -826,10 +826,10 @@
             "file": "RM006_connectivity_template.rq",
             "execute": true,
             "result": "13",
-            "execution_time": 1.04
+            "execution_time": 0.19
         },
-        "timestamp": "2025-03-17T15:15",
-        "file": "14_service.md"
+        "timestamp": "2025-03-18T10:46",
+        "file": "service.md"
     },
     "data_service": {
         "instances": {
@@ -837,21 +837,21 @@
             "file": "RM001_instances_template.rq",
             "execute": true,
             "result": "363632",
-            "execution_time": 0.01
+            "execution_time": 0.25
         },
         "assertions": {
             "name": "Number of Assertions",
             "file": "RM002_assertions_template.rq",
             "execute": true,
             "result": "363633",
-            "execution_time": 0.01
+            "execution_time": 0.2
         },
         "linkage": {
             "name": "Average Linkage",
             "file": "RM003_linkage_template.rq",
             "execute": true,
             "result": 0,
-            "execution_time": 0.01
+            "execution_time": 1.2
         },
         "edges_out": {
             "name": "Edges - outgoing",
@@ -861,14 +861,14 @@
                     "file": "RM004_1_out_edges_total_template.rq",
                     "execute": true,
                     "result": "363632",
-                    "execution_time": 0.01
+                    "execution_time": 1.94
                 },
                 "min": {
                     "name": "Minimum of outgoing edges",
                     "file": "RM004_2_out_edges_min_template.rq",
                     "execute": true,
                     "result": "11",
-                    "execution_time": 0.01
+                    "execution_time": 0.6
                 },
                 "median": {
                     "name": "Median of outgoing edges",
@@ -878,14 +878,14 @@
                     },
                     "execute": true,
                     "result": "28",
-                    "execution_time": 0.01
+                    "execution_time": 0.99
                 },
                 "max": {
                     "name": "Maximum of outgoing edges",
                     "file": "RM004_4_out_edges_max_template.rq",
                     "execute": true,
                     "result": "12519",
-                    "execution_time": 0.01
+                    "execution_time": 0.64
                 }
             }
         },
@@ -897,14 +897,14 @@
                     "file": "RM005_1_in_edges_total_template.rq",
                     "execute": true,
                     "result": "0",
-                    "execution_time": 0.01
+                    "execution_time": 0.24
                 },
                 "min": {
                     "name": "Minimum of incoming edges",
                     "file": "RM005_2_in_edges_min_template.rq",
                     "execute": true,
                     "result": null,
-                    "execution_time": 0.01
+                    "execution_time": 0.22
                 },
                 "median": {
                     "name": "Median of incoming edges",
@@ -914,14 +914,14 @@
                     },
                     "execute": true,
                     "result": null,
-                    "execution_time": 0.01
+                    "execution_time": 0.19
                 },
                 "max": {
                     "name": "Maximum of incoming edges",
                     "file": "RM005_4_in_edges_max_template.rq",
                     "execute": true,
                     "result": null,
-                    "execution_time": 0.01
+                    "execution_time": 0.22
                 }
             }
         },
@@ -930,10 +930,10 @@
             "file": "RM006_connectivity_template.rq",
             "execute": true,
             "result": "58909",
-            "execution_time": 0.02
+            "execution_time": 2.42
         },
-        "timestamp": "2025-03-17T15:15",
-        "file": "15_data_service.md"
+        "timestamp": "2025-03-18T10:46",
+        "file": "data_service.md"
     },
     "aggregator": {
         "instances": {
@@ -941,21 +941,21 @@
             "file": "RM001_instances_template.rq",
             "execute": true,
             "result": "38",
-            "execution_time": 0.01
+            "execution_time": 0.1
         },
         "assertions": {
             "name": "Number of Assertions",
             "file": "RM002_assertions_template.rq",
             "execute": true,
             "result": "39",
-            "execution_time": 0.01
+            "execution_time": 0.12
         },
         "linkage": {
             "name": "Average Linkage",
             "file": "RM003_linkage_template.rq",
             "execute": true,
             "result": "187",
-            "execution_time": 1.05
+            "execution_time": 0.2
         },
         "edges_out": {
             "name": "Edges - outgoing",
@@ -965,14 +965,14 @@
                     "file": "RM004_1_out_edges_total_template.rq",
                     "execute": true,
                     "result": "38",
-                    "execution_time": 0.01
+                    "execution_time": 0.21
                 },
                 "min": {
                     "name": "Minimum of outgoing edges",
                     "file": "RM004_2_out_edges_min_template.rq",
                     "execute": true,
                     "result": "8",
-                    "execution_time": 0.02
+                    "execution_time": 0.16
                 },
                 "median": {
                     "name": "Median of outgoing edges",
@@ -982,14 +982,14 @@
                     },
                     "execute": true,
                     "result": "36",
-                    "execution_time": 0.02
+                    "execution_time": 0.22
                 },
                 "max": {
                     "name": "Maximum of outgoing edges",
                     "file": "RM004_4_out_edges_max_template.rq",
                     "execute": true,
                     "result": "57",
-                    "execution_time": 0.01
+                    "execution_time": 0.22
                 }
             }
         },
@@ -1001,14 +1001,14 @@
                     "file": "RM005_1_in_edges_total_template.rq",
                     "execute": true,
                     "result": "1",
-                    "execution_time": 0.01
+                    "execution_time": 0.22
                 },
                 "min": {
                     "name": "Minimum of incoming edges",
                     "file": "RM005_2_in_edges_min_template.rq",
                     "execute": true,
                     "result": "151",
-                    "execution_time": 0.02
+                    "execution_time": 0.1
                 },
                 "median": {
                     "name": "Median of incoming edges",
@@ -1018,14 +1018,14 @@
                     },
                     "execute": true,
                     "result": "151",
-                    "execution_time": 0.01
+                    "execution_time": 0.2
                 },
                 "max": {
                     "name": "Maximum of incoming edges",
                     "file": "RM005_4_in_edges_max_template.rq",
                     "execute": true,
                     "result": "151",
-                    "execution_time": 0.01
+                    "execution_time": 0.21
                 }
             }
         },
@@ -1034,10 +1034,10 @@
             "file": "RM006_connectivity_template.rq",
             "execute": true,
             "result": "239",
-            "execution_time": 0.01
+            "execution_time": 0.22
         },
-        "timestamp": "2025-03-17T15:15",
-        "file": "16_aggregator.md"
+        "timestamp": "2025-03-18T10:46",
+        "file": "aggregator.md"
     },
     "person": {
         "instances": {
@@ -1045,21 +1045,21 @@
             "file": "RM001_instances_template.rq",
             "execute": true,
             "result": "2180",
-            "execution_time": 0.01
+            "execution_time": 0.22
         },
         "assertions": {
             "name": "Number of Assertions",
             "file": "RM002_assertions_template.rq",
             "execute": true,
             "result": "2181",
-            "execution_time": 0.01
+            "execution_time": 0.22
         },
         "linkage": {
             "name": "Average Linkage",
             "file": "RM003_linkage_template.rq",
             "execute": true,
             "result": "4.628269848554383",
-            "execution_time": 0.01
+            "execution_time": 0.2
         },
         "edges_out": {
             "name": "Edges - outgoing",
@@ -1069,14 +1069,14 @@
                     "file": "RM004_1_out_edges_total_template.rq",
                     "execute": true,
                     "result": "2180",
-                    "execution_time": 0.01
+                    "execution_time": 0.21
                 },
                 "min": {
                     "name": "Minimum of outgoing edges",
                     "file": "RM004_2_out_edges_min_template.rq",
                     "execute": true,
                     "result": "2",
-                    "execution_time": 0.01
+                    "execution_time": 0.14
                 },
                 "median": {
                     "name": "Median of outgoing edges",
@@ -1086,14 +1086,14 @@
                     },
                     "execute": true,
                     "result": "4",
-                    "execution_time": 0.01
+                    "execution_time": 0.23
                 },
                 "max": {
                     "name": "Maximum of outgoing edges",
                     "file": "RM004_4_out_edges_max_template.rq",
                     "execute": true,
                     "result": "7",
-                    "execution_time": 0.01
+                    "execution_time": 0.21
                 }
             }
         },
@@ -1105,14 +1105,14 @@
                     "file": "RM005_1_in_edges_total_template.rq",
                     "execute": true,
                     "result": "2179",
-                    "execution_time": 0.01
+                    "execution_time": 0.19
                 },
                 "min": {
                     "name": "Minimum of incoming edges",
                     "file": "RM005_2_in_edges_min_template.rq",
                     "execute": true,
                     "result": "1",
-                    "execution_time": 0.01
+                    "execution_time": 0.19
                 },
                 "median": {
                     "name": "Median of incoming edges",
@@ -1122,14 +1122,14 @@
                     },
                     "execute": true,
                     "result": "1",
-                    "execution_time": 0.01
+                    "execution_time": 0.18
                 },
                 "max": {
                     "name": "Maximum of incoming edges",
                     "file": "RM005_4_in_edges_max_template.rq",
                     "execute": true,
                     "result": "2",
-                    "execution_time": 0.01
+                    "execution_time": 0.19
                 }
             }
         },
@@ -1138,10 +1138,10 @@
             "file": "RM006_connectivity_template.rq",
             "execute": true,
             "result": "2",
-            "execution_time": 0.01
+            "execution_time": 0.22
         },
-        "timestamp": "2025-03-17T15:15",
-        "file": "17_person.md"
+        "timestamp": "2025-03-18T10:46",
+        "file": "person.md"
     },
     "registry": {
         "instances": {
@@ -1149,21 +1149,21 @@
             "file": "RM001_instances_template.rq",
             "execute": true,
             "result": "5",
-            "execution_time": 0.01
+            "execution_time": 0.21
         },
         "assertions": {
             "name": "Number of Assertions",
             "file": "RM002_assertions_template.rq",
             "execute": true,
             "result": "6",
-            "execution_time": 0.01
+            "execution_time": 0.2
         },
         "linkage": {
             "name": "Average Linkage",
             "file": "RM003_linkage_template.rq",
             "execute": true,
             "result": 0,
-            "execution_time": 0.01
+            "execution_time": 0.22
         },
         "edges_out": {
             "name": "Edges - outgoing",
@@ -1173,14 +1173,14 @@
                     "file": "RM004_1_out_edges_total_template.rq",
                     "execute": true,
                     "result": "5",
-                    "execution_time": 0.01
+                    "execution_time": 0.1
                 },
                 "min": {
                     "name": "Minimum of outgoing edges",
                     "file": "RM004_2_out_edges_min_template.rq",
                     "execute": true,
                     "result": "7",
-                    "execution_time": 0.01
+                    "execution_time": 0.19
                 },
                 "median": {
                     "name": "Median of outgoing edges",
@@ -1190,14 +1190,14 @@
                     },
                     "execute": true,
                     "result": "9",
-                    "execution_time": 0.01
+                    "execution_time": 0.19
                 },
                 "max": {
                     "name": "Maximum of outgoing edges",
                     "file": "RM004_4_out_edges_max_template.rq",
                     "execute": true,
                     "result": "11",
-                    "execution_time": 0.01
+                    "execution_time": 0.16
                 }
             }
         },
@@ -1209,14 +1209,14 @@
                     "file": "RM005_1_in_edges_total_template.rq",
                     "execute": true,
                     "result": "0",
-                    "execution_time": 0.01
+                    "execution_time": 0.13
                 },
                 "min": {
                     "name": "Minimum of incoming edges",
                     "file": "RM005_2_in_edges_min_template.rq",
                     "execute": true,
                     "result": null,
-                    "execution_time": 0.01
+                    "execution_time": 0.2
                 },
                 "median": {
                     "name": "Median of incoming edges",
@@ -1226,14 +1226,14 @@
                     },
                     "execute": true,
                     "result": null,
-                    "execution_time": 0.01
+                    "execution_time": 0.2
                 },
                 "max": {
                     "name": "Maximum of incoming edges",
                     "file": "RM005_4_in_edges_max_template.rq",
                     "execute": true,
                     "result": null,
-                    "execution_time": 0.01
+                    "execution_time": 0.22
                 }
             }
         },
@@ -1242,10 +1242,10 @@
             "file": "RM006_connectivity_template.rq",
             "execute": true,
             "result": "15",
-            "execution_time": 0.01
+            "execution_time": 0.22
         },
-        "timestamp": "2025-03-17T15:15",
-        "file": "18_registry.md"
+        "timestamp": "2025-03-18T10:46",
+        "file": "registry.md"
     },
-    "timestamp": "2025-03-17T15:15"
+    "timestamp": "2025-03-18T10:46"
 }
\ No newline at end of file
diff --git a/scripts/kg_analysis/cli.py b/scripts/kg_analysis/cli.py
index 989caf5..0d06351 100644
--- a/scripts/kg_analysis/cli.py
+++ b/scripts/kg_analysis/cli.py
@@ -12,16 +12,20 @@ from .util import cli_startup
 from .query_runner import QueryRunner  # type: ignore
 from .metrics_runner import MetricsRunner  # type: ignore
 
+from .interfaces import DEFAULT_REPORTS_DIR
+
 log = logging.getLogger(__name__)
 
 
 @click.group()
 @click.option("--debug/--no-debug", "-d", is_flag=True, default=False)
+@click.option("--output", "-o", type=click.Path(), help="Path to save results")
 @click.pass_context
-def main(ctx, debug):
+def main(ctx, debug, output):
     cli_startup(log_level=debug and logging.DEBUG or logging.INFO)
     ctx.ensure_object(dict)
     ctx.obj["DEBUG"] = debug
+    ctx.obj["output"] = output
 
 
 @main.command()
@@ -32,22 +36,29 @@ def main(ctx, debug):
     help="Path to SPARQL query file",
     required=True,
 )
-@click.option("--output", "-o", type=click.Path(), help="Path to save results")
 @click.pass_context
-def query(ctx, query, output):
+def query(ctx, query):
     """Run analysis on the KnowledgeGraph."""
     runner = QueryRunner()
 
     query_path = Path(query)
     try:
-        results = runner.run_metric(
-            query_path, output_path=Path(output) if output else None
+        output_path = (
+            Path(ctx.obj["output"])
+            if ctx.obj["output"]
+            else DEFAULT_REPORTS_DIR
+            / "queries"
+            / query_path.name.replace(".rq", ".json")
         )
+        output_path.parent.mkdir(parents=True, exist_ok=True)
+
+        results = runner.run_metric(query_path, output_path=output_path)
+
         click.echo(
             click.style("Query executed successfully: ", fg="green")
             + click.style(query_path.name, fg="blue")
         )
-        if not output:
+        if not ctx.obj["output"]:
             print_json(data=results)
     except Exception as e:
         click.echo(
@@ -56,8 +67,14 @@ def query(ctx, query, output):
 
 
 @main.command()
-def metrics():
-    MetricsRunner().run()
+@click.pass_context
+def metrics(ctx):
+    """Run all metrics and save reports."""
+    output_path = (
+        Path(ctx.obj["output"]) if ctx.obj["output"] else DEFAULT_REPORTS_DIR
+    )
+    output_path.mkdir(parents=True, exist_ok=True)
+    MetricsRunner(output_dir=output_path).run()
 
 
 if __name__ == "__main__":
diff --git a/scripts/kg_analysis/interfaces/__init__.py b/scripts/kg_analysis/interfaces/__init__.py
index c223999..79b755f 100644
--- a/scripts/kg_analysis/interfaces/__init__.py
+++ b/scripts/kg_analysis/interfaces/__init__.py
@@ -2,3 +2,8 @@ from .complexity import query_templates as complexity_query_templates
 from .general import query_templates as general_query_templates
 from .resources import query_templates as resource_query_templates
 from .resources import resource_types
+
+from pathlib import Path
+import os
+
+DEFAULT_REPORTS_DIR = Path(os.getenv("REPORTS_DIR", "./reports"))
diff --git a/scripts/kg_analysis/metrics_runner.py b/scripts/kg_analysis/metrics_runner.py
index 8f12e9d..b63da0b 100644
--- a/scripts/kg_analysis/metrics_runner.py
+++ b/scripts/kg_analysis/metrics_runner.py
@@ -18,6 +18,7 @@ from .interfaces import (
     general_query_templates,
     resource_query_templates,
     resource_types,
+    DEFAULT_REPORTS_DIR,
 )
 
 log = logging.getLogger(__name__)
@@ -36,10 +37,14 @@ class MetricsRunnerBase(ABC):
     _execution_time: float = 0
     fail: bool = False
 
-    def __init__(self):
+    def __init__(self, output_dir=None):
         self.runner = QueryRunner()
         self.base_query_path = Path("queries/metrics")
-        self.base_output_path = Path("reports/metrics")
+        self.base_output_path = (
+            Path(output_dir) if output_dir else DEFAULT_REPORTS_DIR
+        )
+        print(self.base_output_path)
+        self.base_output_path.mkdir(parents=True, exist_ok=True)
         log.info(f"Running metric: {self.__class__.__name__}")
 
     @property
@@ -55,7 +60,8 @@ class MetricsRunnerBase(ABC):
 
     def get_output_path(self, suffix: str = ".txt"):
         filename = self._output_file or self.query_file.with_suffix(suffix)
-        return self.base_output_path.joinpath(filename)
+        p = self.base_output_path.joinpath(filename)
+        return p
 
     @property
     def output_path(self):
@@ -66,9 +72,10 @@ class MetricsRunnerBase(ABC):
     def output_path_txt(self):
         return self.get_output_path(suffix=".txt")
 
-    @property
-    def output_path_json(self):
-        return self.get_output_path(suffix=".json")
+    # @property
+    # def output_path_json(self):
+    #     breakpoint()
+    #     return self.get_output_path(suffix=".json")
 
     def query_metric(self, query_path: Path, **kwargs) -> Optional[int]:
         """Run a metric query and the result"""
@@ -101,7 +108,7 @@ class MetricsRunnerBase(ABC):
                 )
             log.info(f"Results saved to {self.output_path_txt}")
 
-    def save_to_json(self, dictionary, filename: Optional[str] = None):
+    def save_to_json(self, dictionary: dict, filename: str):
         """
         Saves results dictionary to a JSON file with proper formatting
 
@@ -110,7 +117,7 @@ class MetricsRunnerBase(ABC):
             filename (str, optional): Target filename, uses default if None
         """
         with open(
-            filename or self.output_path_json, "w", encoding="utf-8"
+            self.base_output_path.joinpath(filename), "w", encoding="utf-8"
         ) as f:
             json.dump(dictionary, f, indent=4, ensure_ascii=False)
 
@@ -223,7 +230,7 @@ class MetricsRunner_General(MetricsRunnerBase):
         Returns:
             dict: General metrics results
         """
-        output_path = "reports/metrics/general.json"
+        output_path = "general.json"
         queries = super().run()
         self.save_to_json(queries, output_path)
         return queries
@@ -249,7 +256,7 @@ class MetricsRunner_Resources(MetricsRunnerBase):
             results[resource_type]["file"] = data["file"]
 
         results["timestamp"] = datetime.now().isoformat(timespec="minutes")
-        self.save_to_json(results, "reports/metrics/resources.json")
+        self.save_to_json(results, "resources.json")
         return results
 
 
@@ -310,23 +317,25 @@ class MetricsRunner_Complexity(MetricsRunnerBase):
         queries["schematic_complexity"] = schematic_complexity
 
         # Save the results to a JSON file
-        self.save_to_json(queries, "reports/metrics/complexity.json")
-
+        self.save_to_json(queries, "complexity.json")
         return queries
 
 
 class MetricsRunner(ABC):
     """Main entry point for executing all metrics"""
 
+    def __init__(self, output_dir=None):
+        self.output_dir = output_dir
+
     def run(self):
         """
         Executes both general and resource-specific metrics
         """
         # Run general metrics for entire knowledge graph
-        # MetricsRunner_General().run()
+        # MetricsRunner_General(output_dir=self.output_dir).run()
 
         # Run metrics for specific resource types
-        # MetricsRunner_Resources().run()
+        # MetricsRunner_Resources(output_dir=self.output_dir).run()
 
         # Run metrics on schematic complexity
-        MetricsRunner_Complexity().run()
+        MetricsRunner_Complexity(output_dir=self.output_dir).run()
-- 
GitLab


From 4d814df7aee55623f6b485f1943ec05e864f3744 Mon Sep 17 00:00:00 2001
From: Ralf Klammer <ralf.klammer@tu-dresden.de>
Date: Wed, 19 Mar 2025 16:23:38 +0100
Subject: [PATCH 40/59] [metrics] move MetricsTableRenderer to package + use
 only REPORTS_DIR

---
 docs/macros/main.py                           | 11 +++-------
 scripts/kg_analysis/__init__.py               |  9 +++++---
 scripts/kg_analysis/cli.py                    |  9 ++++----
 scripts/kg_analysis/interfaces/__init__.py    |  5 -----
 scripts/kg_analysis/metrics_runner.py         |  8 +++----
 .../kg_analysis}/table_renderer.py            | 22 ++++++++++++++-----
 6 files changed, 32 insertions(+), 32 deletions(-)
 rename {docs/macros => scripts/kg_analysis}/table_renderer.py (95%)

diff --git a/docs/macros/main.py b/docs/macros/main.py
index 54bf058..76ce0f8 100644
--- a/docs/macros/main.py
+++ b/docs/macros/main.py
@@ -1,13 +1,8 @@
-import json
-import os
-import sys
-from pathlib import Path
+import logging
 
-sys.path.append(os.path.dirname(os.path.abspath(__file__)))
+from kg_analysis.table_renderer import MetricsTableRenderer  # type: ignore
 
-from table_renderer import MetricsTableRenderer
-
-DEFAULT_REPORTS_DIR = Path(os.getenv("REPORTS_DIR", "./reports"))
+log = logging.getLogger(__name__)
 
 
 def define_env(env):
diff --git a/scripts/kg_analysis/__init__.py b/scripts/kg_analysis/__init__.py
index ae88787..231e26c 100644
--- a/scripts/kg_analysis/__init__.py
+++ b/scripts/kg_analysis/__init__.py
@@ -1,8 +1,9 @@
 import os
 
-from rich.console import Console
-from rich import inspect
-from rich import print as rprint
+from pathlib import Path
+from rich.console import Console  # type: ignore
+from rich import inspect  # type: ignore
+from rich import print as rprint  # type: ignore
 
 console = Console()
 
@@ -10,3 +11,5 @@ SPARQL_ENDPOINT = os.getenv(
     "SPARQL_ENDPOINT", "https://sparql.knowledgehub.nfdi4earth.de"
 )
 SPARQL_TIMEOUT = int(os.getenv("SPARQL_TIMEOUT", 120))
+
+REPORTS_DIR = Path(os.getenv("REPORTS_DIR", "./reports/metrics"))
diff --git a/scripts/kg_analysis/cli.py b/scripts/kg_analysis/cli.py
index 0d06351..f6a1de7 100644
--- a/scripts/kg_analysis/cli.py
+++ b/scripts/kg_analysis/cli.py
@@ -8,11 +8,12 @@ import click
 from pathlib import Path
 from rich import print_json
 
+from . import REPORTS_DIR
+
 from .util import cli_startup
 from .query_runner import QueryRunner  # type: ignore
 from .metrics_runner import MetricsRunner  # type: ignore
 
-from .interfaces import DEFAULT_REPORTS_DIR
 
 log = logging.getLogger(__name__)
 
@@ -46,7 +47,7 @@ def query(ctx, query):
         output_path = (
             Path(ctx.obj["output"])
             if ctx.obj["output"]
-            else DEFAULT_REPORTS_DIR
+            else REPORTS_DIR
             / "queries"
             / query_path.name.replace(".rq", ".json")
         )
@@ -70,9 +71,7 @@ def query(ctx, query):
 @click.pass_context
 def metrics(ctx):
     """Run all metrics and save reports."""
-    output_path = (
-        Path(ctx.obj["output"]) if ctx.obj["output"] else DEFAULT_REPORTS_DIR
-    )
+    output_path = Path(ctx.obj["output"]) if ctx.obj["output"] else REPORTS_DIR
     output_path.mkdir(parents=True, exist_ok=True)
     MetricsRunner(output_dir=output_path).run()
 
diff --git a/scripts/kg_analysis/interfaces/__init__.py b/scripts/kg_analysis/interfaces/__init__.py
index 79b755f..c223999 100644
--- a/scripts/kg_analysis/interfaces/__init__.py
+++ b/scripts/kg_analysis/interfaces/__init__.py
@@ -2,8 +2,3 @@ from .complexity import query_templates as complexity_query_templates
 from .general import query_templates as general_query_templates
 from .resources import query_templates as resource_query_templates
 from .resources import resource_types
-
-from pathlib import Path
-import os
-
-DEFAULT_REPORTS_DIR = Path(os.getenv("REPORTS_DIR", "./reports"))
diff --git a/scripts/kg_analysis/metrics_runner.py b/scripts/kg_analysis/metrics_runner.py
index b63da0b..4558fbf 100644
--- a/scripts/kg_analysis/metrics_runner.py
+++ b/scripts/kg_analysis/metrics_runner.py
@@ -12,13 +12,13 @@ from pathlib import Path
 from time import time
 from typing import Optional
 
+from . import REPORTS_DIR
 from .query_runner import QueryRunner
-from .interfaces import (
+from . import (
     complexity_query_templates,
     general_query_templates,
     resource_query_templates,
     resource_types,
-    DEFAULT_REPORTS_DIR,
 )
 
 log = logging.getLogger(__name__)
@@ -40,9 +40,7 @@ class MetricsRunnerBase(ABC):
     def __init__(self, output_dir=None):
         self.runner = QueryRunner()
         self.base_query_path = Path("queries/metrics")
-        self.base_output_path = (
-            Path(output_dir) if output_dir else DEFAULT_REPORTS_DIR
-        )
+        self.base_output_path = Path(output_dir) if output_dir else REPORTS_DIR
         print(self.base_output_path)
         self.base_output_path.mkdir(parents=True, exist_ok=True)
         log.info(f"Running metric: {self.__class__.__name__}")
diff --git a/docs/macros/table_renderer.py b/scripts/kg_analysis/table_renderer.py
similarity index 95%
rename from docs/macros/table_renderer.py
rename to scripts/kg_analysis/table_renderer.py
index b7d19bc..8a676c0 100644
--- a/docs/macros/table_renderer.py
+++ b/scripts/kg_analysis/table_renderer.py
@@ -1,9 +1,13 @@
+# -*- coding: utf-8 -*-
+# Copyright (C) 2025 TU-Dresden, ZIH
+# ralf.klammer@tu-dresden.de
+import logging
+
 import json
-import os
 
-from pathlib import Path
+from . import REPORTS_DIR
 
-REPORTS_DIR = Path(os.getenv("REPORTS_DIR", "./reports/metrics"))
+log = logging.getLogger(__name__)
 
 
 class MetricsTableRenderer:
@@ -102,7 +106,9 @@ class MetricsTableRenderer:
         """Renders the resource overview table."""
         # Get the names of all resources
         resource_names = [
-            key for key, resource in self.data.items() if isinstance(resource, dict)
+            key
+            for key, resource in self.data.items()
+            if isinstance(resource, dict)
         ]
         ordered_resource_names = sorted(resource_names)
 
@@ -129,7 +135,9 @@ class MetricsTableRenderer:
 
         # Construct the table header and rows
         self.output.append(f"| Resource type | {' | '.join(metric_names)} |")
-        self.output.append(f"| --- |{' | '.join(['---' for _ in metric_names])} |")
+        self.output.append(
+            f"| --- |{' | '.join(['---' for _ in metric_names])} |"
+        )
         self.output.extend(rows)
 
     def _render_resource(self, resource_type):
@@ -245,7 +253,9 @@ class MetricsTableRenderer:
                         break
         else:
             # Include all categories except the timestamp
-            categories = {k: v for k, v in self.data.items() if k != "timestamp"}
+            categories = {
+                k: v for k, v in self.data.items() if k != "timestamp"
+            }
 
         # Iterate through the categories
         for data in categories.values():
-- 
GitLab


From f68a2ac27f57fe001d44666a7fa86ad714166816 Mon Sep 17 00:00:00 2001
From: Ralf Klammer <ralf.klammer@tu-dresden.de>
Date: Wed, 19 Mar 2025 16:24:25 +0100
Subject: [PATCH 41/59] [metrics] Remove method 'general_overview'

---
 scripts/kg_analysis/table_renderer.py | 33 ---------------------------
 1 file changed, 33 deletions(-)

diff --git a/scripts/kg_analysis/table_renderer.py b/scripts/kg_analysis/table_renderer.py
index 8a676c0..965fb2d 100644
--- a/scripts/kg_analysis/table_renderer.py
+++ b/scripts/kg_analysis/table_renderer.py
@@ -65,10 +65,6 @@ class MetricsTableRenderer:
                 "method": lambda: self._render_resource(self.resource_type),
                 "report": "resources.json",
             },
-            # "general_overview": {
-            #     "method": self._render_general,
-            #     "report": "general.json",
-            # },
             "general": {
                 "method": self._render_general,
                 "report": "general.json",
@@ -161,35 +157,6 @@ class MetricsTableRenderer:
                         f"| {sub_query_data['name']} | [{sub_query_data['file']}](#{sub_query_data['file']}) | {sub_query_data['result']} |"
                     )
 
-    # def _render_general_overview(self):
-    #     """Renders the general overview table."""
-    #     # Get the sorted list of resource types
-    #     resource_types = sorted([rt for rt in self.data.keys() if rt != "timestamp"])
-    #     metric_names = []
-
-    #     # Collect metric names from the first resource type
-    #     first_rt = self.data[resource_types[0]]
-    #     for query in first_rt.get("queries", {}).values():
-    #         if "name" in query:
-    #             metric_names.append(query["name"])
-
-    #     # Construct the table header
-    #     self.output.append("| Resource Type | " + " | ".join(metric_names) + " |")
-    #     self.output.append("|" + "|".join(["---"] * (len(metric_names) + 1)) + "|")
-
-    #     # Iterate over each resource type to collect results
-    #     for rt in resource_types:
-    #         row = [rt]
-    #         queries = self.data[rt].get("queries", {})
-    #         for metric in metric_names:
-    #             result = "-"
-    #             for q in queries.values():
-    #                 if q.get("name") == metric:
-    #                     result = str(q.get("result", "-"))
-    #                     break
-    #             row.append(result)
-    #         self.output.append("| " + " | ".join(row) + " |")
-
     def _render_general(self):
         """Renders the general table."""
         self.output.append("| Metric | Query | Result |")
-- 
GitLab


From c7ce4cd431e9c55d44659cf78fc7557f980379cc Mon Sep 17 00:00:00 2001
From: Ralf Klammer <ralf.klammer@tu-dresden.de>
Date: Wed, 19 Mar 2025 16:43:30 +0100
Subject: [PATCH 42/59] [metrics] refine comments

---
 docs/macros/main.py | 56 +++++++++++++++++++++++++++++++++++++++++----
 1 file changed, 51 insertions(+), 5 deletions(-)

diff --git a/docs/macros/main.py b/docs/macros/main.py
index 76ce0f8..9c92b78 100644
--- a/docs/macros/main.py
+++ b/docs/macros/main.py
@@ -13,11 +13,12 @@ def define_env(env):
 
         Args:
             filename (str): The path to the file.
-            start_line (int, optional): The line number to start including from.
+            start_line (int, optional): The line number to start from.
             single_line (int, optional): The specific line number to include.
 
         Returns:
-            str: The content of the file or an error message if the file does not exist.
+            str: The content of the file or an error message if the file
+                 does not exist.
         """
         try:
             with open(filename, "r") as f:
@@ -28,14 +29,15 @@ def define_env(env):
                     return lines[single_line]
                 return "".join(lines)
         except FileNotFoundError:
-            return f"*No results available. The file `{filename}` was not found.*"
+            return f"No results available. The file `{filename}` wasnt found.*"
         except IndexError:
             return f"*Error: The specified line {start_line} does not exist in `{filename}`.*"
 
     @env.macro
     def include_template(template_path, resource_type, **kwargs):
         """
-        Includes a template file and replaces {resource_type} with the given value.
+        Includes a template file and replaces {resource_type} with the given
+        value.
 
         Args:
             template_path (str): Path to the template file.
@@ -51,31 +53,75 @@ def define_env(env):
 
     @env.macro
     def metrics_table_single_resource(resource_type=None):
+        """
+        Renders a metrics table for a single resource type.
+
+        Args:
+            resource_type (str, optional): The resource type URI.
+
+        Returns:
+            str: The rendered metrics table.
+        """
         return MetricsTableRenderer(
             table_type="resource", resource_type=resource_type
         ).render()
 
     @env.macro
     def metrics_table_overview_resource():
+        """
+        Renders an overview metrics table for all resource types.
+
+        Returns:
+            str: The rendered overview metrics table.
+        """
         return MetricsTableRenderer(table_type="resource_overview").render()
 
     @env.macro
     def metrics_table_overview_general():
+        """
+        Renders an overview metrics table for general metrics.
+
+        Returns:
+            str: The rendered overview metrics table.
+        """
         return MetricsTableRenderer(table_type="general").render()
 
     @env.macro
     def metrics_table_single_general(metric_key):
-        print(metric_key)
+        """
+        Renders a metrics table for a single general metric.
+
+        Args:
+            metric_key (str): The key of the general metric.
+
+        Returns:
+            str: The rendered metrics table.
+        """
         return MetricsTableRenderer(
             table_type="general", metric_key=metric_key
         ).render()
 
     @env.macro
     def metrics_table_overview_complexity():
+        """
+        Renders an overview metrics table for complexity metrics.
+
+        Returns:
+            str: The rendered overview metrics table.
+        """
         return MetricsTableRenderer(table_type="complexity").render()
 
     @env.macro
     def metrics_table_single_complexity(metric_key):
+        """
+        Renders a metrics table for a single complexity metric.
+
+        Args:
+            metric_key (str): The key of the complexity metric.
+
+        Returns:
+            str: The rendered metrics table.
+        """
         return MetricsTableRenderer(
             table_type="complexity", metric_key=metric_key
         ).render()
-- 
GitLab


From 3012f3d9be4b6f2bf3444987107744aa1cdc7a92 Mon Sep 17 00:00:00 2001
From: Ralf Klammer <ralf.klammer@tu-dresden.de>
Date: Wed, 19 Mar 2025 16:43:51 +0100
Subject: [PATCH 43/59] [metrics] refine comments & unify horizontal table
 rendering

---
 scripts/kg_analysis/table_renderer.py | 92 ++++++++++++---------------
 1 file changed, 39 insertions(+), 53 deletions(-)

diff --git a/scripts/kg_analysis/table_renderer.py b/scripts/kg_analysis/table_renderer.py
index 965fb2d..b2b3e12 100644
--- a/scripts/kg_analysis/table_renderer.py
+++ b/scripts/kg_analysis/table_renderer.py
@@ -80,17 +80,11 @@ class MetricsTableRenderer:
         """
         Renders the table in the desired format.
 
-        Args:
-            table_type (str): Type of table ('general', 'resource', 'general_overview', 'resource_overview')
-            resource_type (str, optional): Specific resource type for resource tables
-
         Returns:
             str: The rendered markdown table
         """
         self.output = []
 
-        # Dictionary mapping table types to their corresponding rendering methods
-
         # Get the appropriate rendering method based on the table type
         self.table_option["method"]()
 
@@ -98,6 +92,45 @@ class MetricsTableRenderer:
         self.output.append(f"\n*Last updated: {self.timestamp}*\n")
         return "\n".join(self.output)
 
+    def _render_horizontal_table(self, data):
+        self.output.append("| Metric | Query (file) | Result |")
+        self.output.append("|--------|------|--------|")
+
+        # Iterate over each query data for the specified resource type
+        for q_data in data.values():
+            if "file" in q_data:
+                row = [
+                    q_data["name"],
+                    f"[{q_data['file']}](#{q_data['file']})",
+                    str(q_data["result"]),
+                ]
+                self.output.append("| " + " | ".join(row) + " |")
+            elif "files" in q_data:
+                self.output.append(f"| **{q_data['name']}** | | |")
+                for sub_q_data in q_data["files"].values():
+                    sub_row = [
+                        sub_q_data["name"],
+                        f"[{sub_q_data['file']}](#{sub_q_data['file']})",
+                        str(sub_q_data["result"]),
+                    ]
+                    self.output.append("| " + " | ".join(sub_row) + " |")
+
+    def _render_resource(self, resource_type):
+        """Renders the resource-specific table - single metric only"""
+        if resource_type not in self.data:
+            return f"*No metrics found for {resource_type}*"
+
+        self._render_horizontal_table(self.data[resource_type])
+
+    def _render_general(self):
+        """Renders a general table - general metrics or a single metric."""
+        data = (
+            {self.metric_key: self.data[self.metric_key]}
+            if self.metric_key
+            else self.data
+        )
+        self._render_horizontal_table(data)
+
     def _render_resource_overview(self):
         """Renders the resource overview table."""
         # Get the names of all resources
@@ -136,53 +169,6 @@ class MetricsTableRenderer:
         )
         self.output.extend(rows)
 
-    def _render_resource(self, resource_type):
-        """Renders the resource-specific table."""
-        if resource_type not in self.data:
-            return f"*No metrics found for {resource_type}*"
-
-        self.output.append("| Metric | Query (file) | Result |")
-        self.output.append("|--------|------|--------|")
-
-        # Iterate over each query data for the specified resource type
-        for query_data in self.data[resource_type].values():
-            if "file" in query_data:
-                self.output.append(
-                    f"| {query_data['name']} | [{query_data['file']}](#{query_data['file']}) | {query_data['result']} |"
-                )
-            elif "files" in query_data:
-                self.output.append(f"| **{query_data['name']}** | | |")
-                for sub_query_data in query_data["files"].values():
-                    self.output.append(
-                        f"| {sub_query_data['name']} | [{sub_query_data['file']}](#{sub_query_data['file']}) | {sub_query_data['result']} |"
-                    )
-
-    def _render_general(self):
-        """Renders the general table."""
-        self.output.append("| Metric | Query | Result |")
-        self.output.append("|--------|-------|--------|")
-
-        # Iterate over each metric data to construct the table rows
-        data = (
-            {self.metric_key: self.data[self.metric_key]}
-            if self.metric_key
-            else self.data
-        )
-        for metric_key, metric_data in data.items():
-            if metric_key == "timestamp":
-                continue
-            if isinstance(metric_data, dict):
-                if "files" in metric_data:
-                    self.output.append(f"| **{metric_data['name']}** | | |")
-                    for sub_query in metric_data["files"].values():
-                        self.output.append(
-                            f"| {sub_query['name']} | {sub_query['file']} | {sub_query.get('result', '-')} |"
-                        )
-                elif "name" in metric_data:
-                    self.output.append(
-                        f"| {metric_data['name']} | {metric_data['file']} | {metric_data.get('result', '-')} |"
-                    )
-
     def _render_complexity(self):
         """Renders a complexity table."""
         # Base header for all tables
-- 
GitLab


From 3a51c39bd1abd41302b07a3ca9a1a098408739ba Mon Sep 17 00:00:00 2001
From: Ralf Klammer <ralf.klammer@tu-dresden.de>
Date: Wed, 19 Mar 2025 16:45:49 +0100
Subject: [PATCH 44/59] [metrics] Remove unnecessary overviews

---
 docs/metrics/general metrics/00_overview.md   | 10 ---
 .../schema_complexity_metrics/00_overview.md  | 63 -------------------
 2 files changed, 73 deletions(-)
 delete mode 100644 docs/metrics/general metrics/00_overview.md
 delete mode 100644 docs/metrics/schema_complexity_metrics/00_overview.md

diff --git a/docs/metrics/general metrics/00_overview.md b/docs/metrics/general metrics/00_overview.md
deleted file mode 100644
index cc412a3..0000000
--- a/docs/metrics/general metrics/00_overview.md	
+++ /dev/null
@@ -1,10 +0,0 @@
-# Overview
-
-These metrics analyze the entire knowledge graph structure and provide insights into:
-
-- Overall size and complexity
-- Connection patterns
-- Graph density and distribution
-- Edge statistics (incoming/outgoing)
-
-{{ metrics_table_overview_general() }}
\ No newline at end of file
diff --git a/docs/metrics/schema_complexity_metrics/00_overview.md b/docs/metrics/schema_complexity_metrics/00_overview.md
deleted file mode 100644
index 27b45fd..0000000
--- a/docs/metrics/schema_complexity_metrics/00_overview.md
+++ /dev/null
@@ -1,63 +0,0 @@
-# Overview
-
-To calculate the complexity of RDF schemas, we combine the presented formality metrics focusing on both structural and semantic aspects.
-
-### 1. Basic Structural Complexity
-
-These metrics measure the size and diversity of the RDF schema:
-
-- **[Number of classes](./schema_complexity_metrics/01_classes)** (_C_) → More classes indicate a more complex schema.
-- **[Number of properties](./schema_complexity_metrics/02_properties)** (_P_) → More properties mean more relationships between entities.
-- **[Average class hierarchy depth](./schema_complexity_metrics/03_depth)** (_D_avg_) → The mean number of hierarchy levels in the schema.
-- **[Average class hierarchy width](./schema_complexity_metrics/04_width)** (_W_avg_) → The mean number of sibling classes at each hierarchy level.
-
-A structural complexity score can be calculated as:
-
-    C_structural = w1 * C + w2 * P + w3 * D_avg + w4 * W_avg
-
-where _w_i_ are weights that determine the relative importance of each factor.
-
-### 2. Semantic Complexity
-
-If OWL is used, the complexity increases due to advanced semantics:
-
-- **[Number of restrictions](./schema_complexity_metrics/05_restrictions)** (_R_) → More restrictions indicate more constraints and rules.
-- **[Number of logical axioms](./schema_complexity_metrics/06_axioms)** (_A_) → More axioms mean more logical statements and inferences.
-
-A semantic complexity score can be calculated as:
-
-    C_semantic = w5 * R + w6 * A
-
-where _w_i_ are weights that determine the relative importance of each factor.
-
-### 3. Combined Formula for Schema Complexity
-
-To create an overall complexity score, we can combine both structural and semantic aspects:
-
-    C_schema = w1 * C + w2 * P + w3 * D_avg + w4 * W_avg + w5 * R + w6 * A
-
-where _w_i_ are weights that determine the relative importance of each factor.
-
-This formula provides a single numerical value representing schema complexity, which can be normalized (e.g., 0–100) for comparison across different RDF schemas.
-
-### References
-
-> !!!Not the final references!!!
-
-1. Structural Metrics for Ontologies:
-
-- Gómez-Pérez et al. (2004) – "Evaluation of Ontologies"
-- Describes metrics like number of classes, hierarchy depth, and relations
-- Source: https://link.springer.com/chapter/10.1007/978-3-540-30202-5_3
-
-2. OWL and Schema Complexity Measurements:
-
-   - Tartir & Arpinar (2010) – "Ontology Evaluation and Ranking using OntoQA"
-   - Develops the OntoQA model combining structural and semantic metrics
-   - Source: https://doi.org/10.1109/ICDEW.2005.43
-
-3. SPARQL Analysis and RDF Complexity:
-
-   - Lanzenberger et al. (2008) – "Ontology Evaluation – State of the Art"
-   - Describes hierarchical depth as key metric for RDF schema complexity
-   - Source: https://doi.org/10.1007/978-3-540-92673-3_10
\ No newline at end of file
-- 
GitLab


From 53e0d7bc61977e3edb451c857cc21fbc2761cb2c Mon Sep 17 00:00:00 2001
From: Ralf Klammer <ralf.klammer@tu-dresden.de>
Date: Thu, 20 Mar 2025 13:58:49 +0100
Subject: [PATCH 45/59] [metrics] Enable CI/CD-pipeline for pages

---
 .gitignore                            |  3 ++-
 .gitlab-ci.yml                        | 25 +++++++++++++++++++++++++
 mkdocs.yml                            |  5 -----
 scripts/kg_analysis/metrics_runner.py |  2 --
 4 files changed, 27 insertions(+), 8 deletions(-)
 create mode 100644 .gitlab-ci.yml

diff --git a/.gitignore b/.gitignore
index 41dd612..594b9a1 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,2 +1,3 @@
 *venv*
-docs/macros/__pycache__/*
\ No newline at end of file
+docs/macros/__pycache__/*
+public/*
\ No newline at end of file
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
new file mode 100644
index 0000000..2b37190
--- /dev/null
+++ b/.gitlab-ci.yml
@@ -0,0 +1,25 @@
+image: python:3.12-alpine
+
+before_script:
+  - pip install -r requirements.txt
+  - pip install -e scripts
+
+test:
+  stage: test
+  script:
+  - mkdocs build --strict --verbose --site-dir test
+  artifacts:
+    paths:
+    - test
+  # rules:
+  #   - if: $CI_COMMIT_REF_NAME != $CI_DEFAULT_BRANCH
+
+pages:
+  stage: deploy
+  script:
+  - mkdocs build --strict --verbose --site-dir public
+  artifacts:
+    paths:
+    - public
+  # rules:
+  #   - if: $CI_COMMIT_REF_NAME == $CI_DEFAULT_BRANCH
diff --git a/mkdocs.yml b/mkdocs.yml
index f6e240f..7b9d85b 100644
--- a/mkdocs.yml
+++ b/mkdocs.yml
@@ -1,10 +1,5 @@
 site_name: NFDI4Earth - KnowledgeGraph - Questions & Metrics
 
-nav:
-  - Home: index.md
-  - Questions: questions.md
-  - Metrics: metrics/
-
 theme:
   name: material
   language: de
diff --git a/scripts/kg_analysis/metrics_runner.py b/scripts/kg_analysis/metrics_runner.py
index 4558fbf..9e39983 100644
--- a/scripts/kg_analysis/metrics_runner.py
+++ b/scripts/kg_analysis/metrics_runner.py
@@ -41,7 +41,6 @@ class MetricsRunnerBase(ABC):
         self.runner = QueryRunner()
         self.base_query_path = Path("queries/metrics")
         self.base_output_path = Path(output_dir) if output_dir else REPORTS_DIR
-        print(self.base_output_path)
         self.base_output_path.mkdir(parents=True, exist_ok=True)
         log.info(f"Running metric: {self.__class__.__name__}")
 
@@ -267,7 +266,6 @@ class MetricsRunner_Complexity(MetricsRunnerBase):
         total = 0.0
         for metric, data in category_data["files"].items():
             if "result" in data and data["result"] is not None:
-                print(data)
                 try:
                     value = float(data["result"])
                     weight = float(data.get("weight", 1.0))
-- 
GitLab


From bb30f65dea4a4570ef0c504d18680f6053e89541 Mon Sep 17 00:00:00 2001
From: Ralf Klammer <ralf.klammer@tu-dresden.de>
Date: Thu, 20 Mar 2025 15:06:11 +0100
Subject: [PATCH 46/59] [metrics] Move queries to docs/queries

---
 docs/macros/main.py                                    | 10 ++++++++--
 .../queries}/examples/Educational resources.rq         |  0
 {queries => docs/queries}/examples/Metadata.rq         |  0
 .../queries}/examples/Services in the NFDI4Earth.rq    |  0
 {queries => docs/queries}/metrics/GM001.rq             |  0
 {queries => docs/queries}/metrics/GM002_1.rq           |  0
 {queries => docs/queries}/metrics/GM002_2.rq           |  0
 {queries => docs/queries}/metrics/GM002_3.rq           |  0
 {queries => docs/queries}/metrics/GM003_1.rq           |  0
 {queries => docs/queries}/metrics/GM003_2.rq           |  0
 {queries => docs/queries}/metrics/GM003_3.rq           |  0
 {queries => docs/queries}/metrics/GM003_4.rq           |  0
 {queries => docs/queries}/metrics/GM004_1.rq           |  0
 {queries => docs/queries}/metrics/GM004_2.rq           |  0
 {queries => docs/queries}/metrics/GM004_3.rq           |  0
 {queries => docs/queries}/metrics/GM004_4.rq           |  0
 {queries => docs/queries}/metrics/GM004_5.rq           |  0
 {queries => docs/queries}/metrics/GM004_6.rq           |  0
 {queries => docs/queries}/metrics/GM005_1.rq           |  0
 {queries => docs/queries}/metrics/GM005_2.rq           |  0
 {queries => docs/queries}/metrics/GM005_3.rq           |  0
 {queries => docs/queries}/metrics/GM005_4.rq           |  0
 {queries => docs/queries}/metrics/GM005_5.rq           |  0
 {queries => docs/queries}/metrics/GM005_6.rq           |  0
 {queries => docs/queries}/metrics/RF_001.rq            |  0
 {queries => docs/queries}/metrics/RF_002_1.rq          |  0
 {queries => docs/queries}/metrics/RF_002_2.rq          |  0
 {queries => docs/queries}/metrics/RF_002_3.rq          |  0
 {queries => docs/queries}/metrics/RF_002_4.rq          |  0
 {queries => docs/queries}/metrics/RF_002_5.rq          |  0
 {queries => docs/queries}/metrics/RF_002_6.rq          |  0
 {queries => docs/queries}/metrics/RF_003.rq            |  0
 {queries => docs/queries}/metrics/RF_004.rq            |  0
 {queries => docs/queries}/metrics/RF_005.rq            |  0
 {queries => docs/queries}/metrics/RF_006.rq            |  0
 .../queries}/metrics/RM001_instances_template.rq       |  0
 .../queries}/metrics/RM002_assertions_template.rq      |  0
 .../queries}/metrics/RM003_linkage_template.rq         |  0
 .../metrics/RM004_1_out_edges_total_template.rq        |  0
 .../queries}/metrics/RM004_2_out_edges_min_template.rq |  0
 .../metrics/RM004_3_out_edges_median_template.rq       |  0
 .../queries}/metrics/RM004_4_out_edges_max_template.rq |  0
 .../metrics/RM005_1_in_edges_total_template.rq         |  0
 .../queries}/metrics/RM005_2_in_edges_min_template.rq  |  0
 .../metrics/RM005_3_in_edges_median_template.rq        |  0
 .../queries}/metrics/RM005_4_in_edges_max_template.rq  |  0
 .../queries}/metrics/RM006_connectivity_template.rq    |  0
 {queries => docs/queries}/questions/AG001.rq           |  0
 {queries => docs/queries}/questions/AG001_2.rq         |  0
 {queries => docs/queries}/questions/AG002_1.rq         |  0
 {queries => docs/queries}/questions/AG002_2.rq         |  0
 {queries => docs/queries}/questions/AT001.rq           |  0
 {queries => docs/queries}/questions/AT002_1.rq         |  0
 {queries => docs/queries}/questions/AT002_2.rq         |  0
 {queries => docs/queries}/questions/DA001.rq           |  0
 {queries => docs/queries}/questions/DA002_1.rq         |  0
 {queries => docs/queries}/questions/DA002_2.rq         |  0
 {queries => docs/queries}/questions/DA003_1.rq         |  0
 {queries => docs/queries}/questions/LH001.rq           |  0
 {queries => docs/queries}/questions/LH002_1.rq         |  0
 {queries => docs/queries}/questions/LH002_2.rq         |  0
 {queries => docs/queries}/questions/LR001.rq           |  0
 {queries => docs/queries}/questions/LR002_1.rq         |  0
 {queries => docs/queries}/questions/LR002_2.rq         |  0
 {queries => docs/queries}/questions/MS001.rq           |  0
 {queries => docs/queries}/questions/MS002_1.rq         |  0
 {queries => docs/queries}/questions/MS002_2.rq         |  0
 {queries => docs/queries}/questions/OG001.rq           |  0
 {queries => docs/queries}/questions/OG002_1.rq         |  0
 {queries => docs/queries}/questions/OG002_2.rq         |  0
 {queries => docs/queries}/questions/PE001.rq           |  0
 {queries => docs/queries}/questions/PE002_1.rq         |  0
 {queries => docs/queries}/questions/PE002_2.rq         |  0
 {queries => docs/queries}/questions/REG001.rq          |  0
 {queries => docs/queries}/questions/REG002_1.rq        |  0
 {queries => docs/queries}/questions/REG002_2.rq        |  0
 {queries => docs/queries}/questions/REP001.rq          |  0
 {queries => docs/queries}/questions/REP002_1.rq        |  0
 {queries => docs/queries}/questions/REP002_2.rq        |  0
 {queries => docs/queries}/questions/RP001.rq           |  0
 {queries => docs/queries}/questions/RP002_1.rq         |  0
 {queries => docs/queries}/questions/RP002_2.rq         |  0
 {queries => docs/queries}/questions/SC001.rq           |  0
 {queries => docs/queries}/questions/SC002_1.rq         |  0
 {queries => docs/queries}/questions/SC002_2.rq         |  0
 {queries => docs/queries}/questions/TY001.rq           |  0
 {queries => docs/queries}/questions/old/DR001_1.rq     |  0
 {queries => docs/queries}/questions/old/OR001_1.rq     |  0
 {queries => docs/queries}/questions/old/OR001_2.rq     |  0
 {queries => docs/queries}/questions/old/OR002_1.rq     |  0
 {queries => docs/queries}/questions/old/OR003_1.rq     |  0
 {queries => docs/queries}/questions/old/OR004_1.rq     |  0
 {queries => docs/queries}/questions/old/OR005_1.rq     |  0
 {queries => docs/queries}/questions/old/OR006_1.rq     |  0
 scripts/kg_analysis/metrics_runner.py                  |  2 +-
 95 files changed, 9 insertions(+), 3 deletions(-)
 rename {queries => docs/queries}/examples/Educational resources.rq (100%)
 rename {queries => docs/queries}/examples/Metadata.rq (100%)
 rename {queries => docs/queries}/examples/Services in the NFDI4Earth.rq (100%)
 rename {queries => docs/queries}/metrics/GM001.rq (100%)
 rename {queries => docs/queries}/metrics/GM002_1.rq (100%)
 rename {queries => docs/queries}/metrics/GM002_2.rq (100%)
 rename {queries => docs/queries}/metrics/GM002_3.rq (100%)
 rename {queries => docs/queries}/metrics/GM003_1.rq (100%)
 rename {queries => docs/queries}/metrics/GM003_2.rq (100%)
 rename {queries => docs/queries}/metrics/GM003_3.rq (100%)
 rename {queries => docs/queries}/metrics/GM003_4.rq (100%)
 rename {queries => docs/queries}/metrics/GM004_1.rq (100%)
 rename {queries => docs/queries}/metrics/GM004_2.rq (100%)
 rename {queries => docs/queries}/metrics/GM004_3.rq (100%)
 rename {queries => docs/queries}/metrics/GM004_4.rq (100%)
 rename {queries => docs/queries}/metrics/GM004_5.rq (100%)
 rename {queries => docs/queries}/metrics/GM004_6.rq (100%)
 rename {queries => docs/queries}/metrics/GM005_1.rq (100%)
 rename {queries => docs/queries}/metrics/GM005_2.rq (100%)
 rename {queries => docs/queries}/metrics/GM005_3.rq (100%)
 rename {queries => docs/queries}/metrics/GM005_4.rq (100%)
 rename {queries => docs/queries}/metrics/GM005_5.rq (100%)
 rename {queries => docs/queries}/metrics/GM005_6.rq (100%)
 rename {queries => docs/queries}/metrics/RF_001.rq (100%)
 rename {queries => docs/queries}/metrics/RF_002_1.rq (100%)
 rename {queries => docs/queries}/metrics/RF_002_2.rq (100%)
 rename {queries => docs/queries}/metrics/RF_002_3.rq (100%)
 rename {queries => docs/queries}/metrics/RF_002_4.rq (100%)
 rename {queries => docs/queries}/metrics/RF_002_5.rq (100%)
 rename {queries => docs/queries}/metrics/RF_002_6.rq (100%)
 rename {queries => docs/queries}/metrics/RF_003.rq (100%)
 rename {queries => docs/queries}/metrics/RF_004.rq (100%)
 rename {queries => docs/queries}/metrics/RF_005.rq (100%)
 rename {queries => docs/queries}/metrics/RF_006.rq (100%)
 rename {queries => docs/queries}/metrics/RM001_instances_template.rq (100%)
 rename {queries => docs/queries}/metrics/RM002_assertions_template.rq (100%)
 rename {queries => docs/queries}/metrics/RM003_linkage_template.rq (100%)
 rename {queries => docs/queries}/metrics/RM004_1_out_edges_total_template.rq (100%)
 rename {queries => docs/queries}/metrics/RM004_2_out_edges_min_template.rq (100%)
 rename {queries => docs/queries}/metrics/RM004_3_out_edges_median_template.rq (100%)
 rename {queries => docs/queries}/metrics/RM004_4_out_edges_max_template.rq (100%)
 rename {queries => docs/queries}/metrics/RM005_1_in_edges_total_template.rq (100%)
 rename {queries => docs/queries}/metrics/RM005_2_in_edges_min_template.rq (100%)
 rename {queries => docs/queries}/metrics/RM005_3_in_edges_median_template.rq (100%)
 rename {queries => docs/queries}/metrics/RM005_4_in_edges_max_template.rq (100%)
 rename {queries => docs/queries}/metrics/RM006_connectivity_template.rq (100%)
 rename {queries => docs/queries}/questions/AG001.rq (100%)
 rename {queries => docs/queries}/questions/AG001_2.rq (100%)
 rename {queries => docs/queries}/questions/AG002_1.rq (100%)
 rename {queries => docs/queries}/questions/AG002_2.rq (100%)
 rename {queries => docs/queries}/questions/AT001.rq (100%)
 rename {queries => docs/queries}/questions/AT002_1.rq (100%)
 rename {queries => docs/queries}/questions/AT002_2.rq (100%)
 rename {queries => docs/queries}/questions/DA001.rq (100%)
 rename {queries => docs/queries}/questions/DA002_1.rq (100%)
 rename {queries => docs/queries}/questions/DA002_2.rq (100%)
 rename {queries => docs/queries}/questions/DA003_1.rq (100%)
 rename {queries => docs/queries}/questions/LH001.rq (100%)
 rename {queries => docs/queries}/questions/LH002_1.rq (100%)
 rename {queries => docs/queries}/questions/LH002_2.rq (100%)
 rename {queries => docs/queries}/questions/LR001.rq (100%)
 rename {queries => docs/queries}/questions/LR002_1.rq (100%)
 rename {queries => docs/queries}/questions/LR002_2.rq (100%)
 rename {queries => docs/queries}/questions/MS001.rq (100%)
 rename {queries => docs/queries}/questions/MS002_1.rq (100%)
 rename {queries => docs/queries}/questions/MS002_2.rq (100%)
 rename {queries => docs/queries}/questions/OG001.rq (100%)
 rename {queries => docs/queries}/questions/OG002_1.rq (100%)
 rename {queries => docs/queries}/questions/OG002_2.rq (100%)
 rename {queries => docs/queries}/questions/PE001.rq (100%)
 rename {queries => docs/queries}/questions/PE002_1.rq (100%)
 rename {queries => docs/queries}/questions/PE002_2.rq (100%)
 rename {queries => docs/queries}/questions/REG001.rq (100%)
 rename {queries => docs/queries}/questions/REG002_1.rq (100%)
 rename {queries => docs/queries}/questions/REG002_2.rq (100%)
 rename {queries => docs/queries}/questions/REP001.rq (100%)
 rename {queries => docs/queries}/questions/REP002_1.rq (100%)
 rename {queries => docs/queries}/questions/REP002_2.rq (100%)
 rename {queries => docs/queries}/questions/RP001.rq (100%)
 rename {queries => docs/queries}/questions/RP002_1.rq (100%)
 rename {queries => docs/queries}/questions/RP002_2.rq (100%)
 rename {queries => docs/queries}/questions/SC001.rq (100%)
 rename {queries => docs/queries}/questions/SC002_1.rq (100%)
 rename {queries => docs/queries}/questions/SC002_2.rq (100%)
 rename {queries => docs/queries}/questions/TY001.rq (100%)
 rename {queries => docs/queries}/questions/old/DR001_1.rq (100%)
 rename {queries => docs/queries}/questions/old/OR001_1.rq (100%)
 rename {queries => docs/queries}/questions/old/OR001_2.rq (100%)
 rename {queries => docs/queries}/questions/old/OR002_1.rq (100%)
 rename {queries => docs/queries}/questions/old/OR003_1.rq (100%)
 rename {queries => docs/queries}/questions/old/OR004_1.rq (100%)
 rename {queries => docs/queries}/questions/old/OR005_1.rq (100%)
 rename {queries => docs/queries}/questions/old/OR006_1.rq (100%)

diff --git a/docs/macros/main.py b/docs/macros/main.py
index 9c92b78..86b5886 100644
--- a/docs/macros/main.py
+++ b/docs/macros/main.py
@@ -7,7 +7,12 @@ log = logging.getLogger(__name__)
 
 def define_env(env):
     @env.macro
-    def include_if_exists(filename, start_line=None, single_line=None):
+    def include_if_exists(
+        filename,
+        path="docs/queries",
+        start_line=None,
+        single_line=None,
+    ):
         """
         Includes the content of a file if it exists.
 
@@ -21,6 +26,7 @@ def define_env(env):
                  does not exist.
         """
         try:
+            filename = f"{path}/{filename}"
             with open(filename, "r") as f:
                 lines = f.readlines()
                 if start_line is not None:
@@ -29,7 +35,7 @@ def define_env(env):
                     return lines[single_line]
                 return "".join(lines)
         except FileNotFoundError:
-            return f"No results available. The file `{filename}` wasnt found.*"
+            return f"No results available. The file `{filename}` wasn't found.*"
         except IndexError:
             return f"*Error: The specified line {start_line} does not exist in `{filename}`.*"
 
diff --git a/queries/examples/Educational resources.rq b/docs/queries/examples/Educational resources.rq
similarity index 100%
rename from queries/examples/Educational resources.rq
rename to docs/queries/examples/Educational resources.rq
diff --git a/queries/examples/Metadata.rq b/docs/queries/examples/Metadata.rq
similarity index 100%
rename from queries/examples/Metadata.rq
rename to docs/queries/examples/Metadata.rq
diff --git a/queries/examples/Services in the NFDI4Earth.rq b/docs/queries/examples/Services in the NFDI4Earth.rq
similarity index 100%
rename from queries/examples/Services in the NFDI4Earth.rq
rename to docs/queries/examples/Services in the NFDI4Earth.rq
diff --git a/queries/metrics/GM001.rq b/docs/queries/metrics/GM001.rq
similarity index 100%
rename from queries/metrics/GM001.rq
rename to docs/queries/metrics/GM001.rq
diff --git a/queries/metrics/GM002_1.rq b/docs/queries/metrics/GM002_1.rq
similarity index 100%
rename from queries/metrics/GM002_1.rq
rename to docs/queries/metrics/GM002_1.rq
diff --git a/queries/metrics/GM002_2.rq b/docs/queries/metrics/GM002_2.rq
similarity index 100%
rename from queries/metrics/GM002_2.rq
rename to docs/queries/metrics/GM002_2.rq
diff --git a/queries/metrics/GM002_3.rq b/docs/queries/metrics/GM002_3.rq
similarity index 100%
rename from queries/metrics/GM002_3.rq
rename to docs/queries/metrics/GM002_3.rq
diff --git a/queries/metrics/GM003_1.rq b/docs/queries/metrics/GM003_1.rq
similarity index 100%
rename from queries/metrics/GM003_1.rq
rename to docs/queries/metrics/GM003_1.rq
diff --git a/queries/metrics/GM003_2.rq b/docs/queries/metrics/GM003_2.rq
similarity index 100%
rename from queries/metrics/GM003_2.rq
rename to docs/queries/metrics/GM003_2.rq
diff --git a/queries/metrics/GM003_3.rq b/docs/queries/metrics/GM003_3.rq
similarity index 100%
rename from queries/metrics/GM003_3.rq
rename to docs/queries/metrics/GM003_3.rq
diff --git a/queries/metrics/GM003_4.rq b/docs/queries/metrics/GM003_4.rq
similarity index 100%
rename from queries/metrics/GM003_4.rq
rename to docs/queries/metrics/GM003_4.rq
diff --git a/queries/metrics/GM004_1.rq b/docs/queries/metrics/GM004_1.rq
similarity index 100%
rename from queries/metrics/GM004_1.rq
rename to docs/queries/metrics/GM004_1.rq
diff --git a/queries/metrics/GM004_2.rq b/docs/queries/metrics/GM004_2.rq
similarity index 100%
rename from queries/metrics/GM004_2.rq
rename to docs/queries/metrics/GM004_2.rq
diff --git a/queries/metrics/GM004_3.rq b/docs/queries/metrics/GM004_3.rq
similarity index 100%
rename from queries/metrics/GM004_3.rq
rename to docs/queries/metrics/GM004_3.rq
diff --git a/queries/metrics/GM004_4.rq b/docs/queries/metrics/GM004_4.rq
similarity index 100%
rename from queries/metrics/GM004_4.rq
rename to docs/queries/metrics/GM004_4.rq
diff --git a/queries/metrics/GM004_5.rq b/docs/queries/metrics/GM004_5.rq
similarity index 100%
rename from queries/metrics/GM004_5.rq
rename to docs/queries/metrics/GM004_5.rq
diff --git a/queries/metrics/GM004_6.rq b/docs/queries/metrics/GM004_6.rq
similarity index 100%
rename from queries/metrics/GM004_6.rq
rename to docs/queries/metrics/GM004_6.rq
diff --git a/queries/metrics/GM005_1.rq b/docs/queries/metrics/GM005_1.rq
similarity index 100%
rename from queries/metrics/GM005_1.rq
rename to docs/queries/metrics/GM005_1.rq
diff --git a/queries/metrics/GM005_2.rq b/docs/queries/metrics/GM005_2.rq
similarity index 100%
rename from queries/metrics/GM005_2.rq
rename to docs/queries/metrics/GM005_2.rq
diff --git a/queries/metrics/GM005_3.rq b/docs/queries/metrics/GM005_3.rq
similarity index 100%
rename from queries/metrics/GM005_3.rq
rename to docs/queries/metrics/GM005_3.rq
diff --git a/queries/metrics/GM005_4.rq b/docs/queries/metrics/GM005_4.rq
similarity index 100%
rename from queries/metrics/GM005_4.rq
rename to docs/queries/metrics/GM005_4.rq
diff --git a/queries/metrics/GM005_5.rq b/docs/queries/metrics/GM005_5.rq
similarity index 100%
rename from queries/metrics/GM005_5.rq
rename to docs/queries/metrics/GM005_5.rq
diff --git a/queries/metrics/GM005_6.rq b/docs/queries/metrics/GM005_6.rq
similarity index 100%
rename from queries/metrics/GM005_6.rq
rename to docs/queries/metrics/GM005_6.rq
diff --git a/queries/metrics/RF_001.rq b/docs/queries/metrics/RF_001.rq
similarity index 100%
rename from queries/metrics/RF_001.rq
rename to docs/queries/metrics/RF_001.rq
diff --git a/queries/metrics/RF_002_1.rq b/docs/queries/metrics/RF_002_1.rq
similarity index 100%
rename from queries/metrics/RF_002_1.rq
rename to docs/queries/metrics/RF_002_1.rq
diff --git a/queries/metrics/RF_002_2.rq b/docs/queries/metrics/RF_002_2.rq
similarity index 100%
rename from queries/metrics/RF_002_2.rq
rename to docs/queries/metrics/RF_002_2.rq
diff --git a/queries/metrics/RF_002_3.rq b/docs/queries/metrics/RF_002_3.rq
similarity index 100%
rename from queries/metrics/RF_002_3.rq
rename to docs/queries/metrics/RF_002_3.rq
diff --git a/queries/metrics/RF_002_4.rq b/docs/queries/metrics/RF_002_4.rq
similarity index 100%
rename from queries/metrics/RF_002_4.rq
rename to docs/queries/metrics/RF_002_4.rq
diff --git a/queries/metrics/RF_002_5.rq b/docs/queries/metrics/RF_002_5.rq
similarity index 100%
rename from queries/metrics/RF_002_5.rq
rename to docs/queries/metrics/RF_002_5.rq
diff --git a/queries/metrics/RF_002_6.rq b/docs/queries/metrics/RF_002_6.rq
similarity index 100%
rename from queries/metrics/RF_002_6.rq
rename to docs/queries/metrics/RF_002_6.rq
diff --git a/queries/metrics/RF_003.rq b/docs/queries/metrics/RF_003.rq
similarity index 100%
rename from queries/metrics/RF_003.rq
rename to docs/queries/metrics/RF_003.rq
diff --git a/queries/metrics/RF_004.rq b/docs/queries/metrics/RF_004.rq
similarity index 100%
rename from queries/metrics/RF_004.rq
rename to docs/queries/metrics/RF_004.rq
diff --git a/queries/metrics/RF_005.rq b/docs/queries/metrics/RF_005.rq
similarity index 100%
rename from queries/metrics/RF_005.rq
rename to docs/queries/metrics/RF_005.rq
diff --git a/queries/metrics/RF_006.rq b/docs/queries/metrics/RF_006.rq
similarity index 100%
rename from queries/metrics/RF_006.rq
rename to docs/queries/metrics/RF_006.rq
diff --git a/queries/metrics/RM001_instances_template.rq b/docs/queries/metrics/RM001_instances_template.rq
similarity index 100%
rename from queries/metrics/RM001_instances_template.rq
rename to docs/queries/metrics/RM001_instances_template.rq
diff --git a/queries/metrics/RM002_assertions_template.rq b/docs/queries/metrics/RM002_assertions_template.rq
similarity index 100%
rename from queries/metrics/RM002_assertions_template.rq
rename to docs/queries/metrics/RM002_assertions_template.rq
diff --git a/queries/metrics/RM003_linkage_template.rq b/docs/queries/metrics/RM003_linkage_template.rq
similarity index 100%
rename from queries/metrics/RM003_linkage_template.rq
rename to docs/queries/metrics/RM003_linkage_template.rq
diff --git a/queries/metrics/RM004_1_out_edges_total_template.rq b/docs/queries/metrics/RM004_1_out_edges_total_template.rq
similarity index 100%
rename from queries/metrics/RM004_1_out_edges_total_template.rq
rename to docs/queries/metrics/RM004_1_out_edges_total_template.rq
diff --git a/queries/metrics/RM004_2_out_edges_min_template.rq b/docs/queries/metrics/RM004_2_out_edges_min_template.rq
similarity index 100%
rename from queries/metrics/RM004_2_out_edges_min_template.rq
rename to docs/queries/metrics/RM004_2_out_edges_min_template.rq
diff --git a/queries/metrics/RM004_3_out_edges_median_template.rq b/docs/queries/metrics/RM004_3_out_edges_median_template.rq
similarity index 100%
rename from queries/metrics/RM004_3_out_edges_median_template.rq
rename to docs/queries/metrics/RM004_3_out_edges_median_template.rq
diff --git a/queries/metrics/RM004_4_out_edges_max_template.rq b/docs/queries/metrics/RM004_4_out_edges_max_template.rq
similarity index 100%
rename from queries/metrics/RM004_4_out_edges_max_template.rq
rename to docs/queries/metrics/RM004_4_out_edges_max_template.rq
diff --git a/queries/metrics/RM005_1_in_edges_total_template.rq b/docs/queries/metrics/RM005_1_in_edges_total_template.rq
similarity index 100%
rename from queries/metrics/RM005_1_in_edges_total_template.rq
rename to docs/queries/metrics/RM005_1_in_edges_total_template.rq
diff --git a/queries/metrics/RM005_2_in_edges_min_template.rq b/docs/queries/metrics/RM005_2_in_edges_min_template.rq
similarity index 100%
rename from queries/metrics/RM005_2_in_edges_min_template.rq
rename to docs/queries/metrics/RM005_2_in_edges_min_template.rq
diff --git a/queries/metrics/RM005_3_in_edges_median_template.rq b/docs/queries/metrics/RM005_3_in_edges_median_template.rq
similarity index 100%
rename from queries/metrics/RM005_3_in_edges_median_template.rq
rename to docs/queries/metrics/RM005_3_in_edges_median_template.rq
diff --git a/queries/metrics/RM005_4_in_edges_max_template.rq b/docs/queries/metrics/RM005_4_in_edges_max_template.rq
similarity index 100%
rename from queries/metrics/RM005_4_in_edges_max_template.rq
rename to docs/queries/metrics/RM005_4_in_edges_max_template.rq
diff --git a/queries/metrics/RM006_connectivity_template.rq b/docs/queries/metrics/RM006_connectivity_template.rq
similarity index 100%
rename from queries/metrics/RM006_connectivity_template.rq
rename to docs/queries/metrics/RM006_connectivity_template.rq
diff --git a/queries/questions/AG001.rq b/docs/queries/questions/AG001.rq
similarity index 100%
rename from queries/questions/AG001.rq
rename to docs/queries/questions/AG001.rq
diff --git a/queries/questions/AG001_2.rq b/docs/queries/questions/AG001_2.rq
similarity index 100%
rename from queries/questions/AG001_2.rq
rename to docs/queries/questions/AG001_2.rq
diff --git a/queries/questions/AG002_1.rq b/docs/queries/questions/AG002_1.rq
similarity index 100%
rename from queries/questions/AG002_1.rq
rename to docs/queries/questions/AG002_1.rq
diff --git a/queries/questions/AG002_2.rq b/docs/queries/questions/AG002_2.rq
similarity index 100%
rename from queries/questions/AG002_2.rq
rename to docs/queries/questions/AG002_2.rq
diff --git a/queries/questions/AT001.rq b/docs/queries/questions/AT001.rq
similarity index 100%
rename from queries/questions/AT001.rq
rename to docs/queries/questions/AT001.rq
diff --git a/queries/questions/AT002_1.rq b/docs/queries/questions/AT002_1.rq
similarity index 100%
rename from queries/questions/AT002_1.rq
rename to docs/queries/questions/AT002_1.rq
diff --git a/queries/questions/AT002_2.rq b/docs/queries/questions/AT002_2.rq
similarity index 100%
rename from queries/questions/AT002_2.rq
rename to docs/queries/questions/AT002_2.rq
diff --git a/queries/questions/DA001.rq b/docs/queries/questions/DA001.rq
similarity index 100%
rename from queries/questions/DA001.rq
rename to docs/queries/questions/DA001.rq
diff --git a/queries/questions/DA002_1.rq b/docs/queries/questions/DA002_1.rq
similarity index 100%
rename from queries/questions/DA002_1.rq
rename to docs/queries/questions/DA002_1.rq
diff --git a/queries/questions/DA002_2.rq b/docs/queries/questions/DA002_2.rq
similarity index 100%
rename from queries/questions/DA002_2.rq
rename to docs/queries/questions/DA002_2.rq
diff --git a/queries/questions/DA003_1.rq b/docs/queries/questions/DA003_1.rq
similarity index 100%
rename from queries/questions/DA003_1.rq
rename to docs/queries/questions/DA003_1.rq
diff --git a/queries/questions/LH001.rq b/docs/queries/questions/LH001.rq
similarity index 100%
rename from queries/questions/LH001.rq
rename to docs/queries/questions/LH001.rq
diff --git a/queries/questions/LH002_1.rq b/docs/queries/questions/LH002_1.rq
similarity index 100%
rename from queries/questions/LH002_1.rq
rename to docs/queries/questions/LH002_1.rq
diff --git a/queries/questions/LH002_2.rq b/docs/queries/questions/LH002_2.rq
similarity index 100%
rename from queries/questions/LH002_2.rq
rename to docs/queries/questions/LH002_2.rq
diff --git a/queries/questions/LR001.rq b/docs/queries/questions/LR001.rq
similarity index 100%
rename from queries/questions/LR001.rq
rename to docs/queries/questions/LR001.rq
diff --git a/queries/questions/LR002_1.rq b/docs/queries/questions/LR002_1.rq
similarity index 100%
rename from queries/questions/LR002_1.rq
rename to docs/queries/questions/LR002_1.rq
diff --git a/queries/questions/LR002_2.rq b/docs/queries/questions/LR002_2.rq
similarity index 100%
rename from queries/questions/LR002_2.rq
rename to docs/queries/questions/LR002_2.rq
diff --git a/queries/questions/MS001.rq b/docs/queries/questions/MS001.rq
similarity index 100%
rename from queries/questions/MS001.rq
rename to docs/queries/questions/MS001.rq
diff --git a/queries/questions/MS002_1.rq b/docs/queries/questions/MS002_1.rq
similarity index 100%
rename from queries/questions/MS002_1.rq
rename to docs/queries/questions/MS002_1.rq
diff --git a/queries/questions/MS002_2.rq b/docs/queries/questions/MS002_2.rq
similarity index 100%
rename from queries/questions/MS002_2.rq
rename to docs/queries/questions/MS002_2.rq
diff --git a/queries/questions/OG001.rq b/docs/queries/questions/OG001.rq
similarity index 100%
rename from queries/questions/OG001.rq
rename to docs/queries/questions/OG001.rq
diff --git a/queries/questions/OG002_1.rq b/docs/queries/questions/OG002_1.rq
similarity index 100%
rename from queries/questions/OG002_1.rq
rename to docs/queries/questions/OG002_1.rq
diff --git a/queries/questions/OG002_2.rq b/docs/queries/questions/OG002_2.rq
similarity index 100%
rename from queries/questions/OG002_2.rq
rename to docs/queries/questions/OG002_2.rq
diff --git a/queries/questions/PE001.rq b/docs/queries/questions/PE001.rq
similarity index 100%
rename from queries/questions/PE001.rq
rename to docs/queries/questions/PE001.rq
diff --git a/queries/questions/PE002_1.rq b/docs/queries/questions/PE002_1.rq
similarity index 100%
rename from queries/questions/PE002_1.rq
rename to docs/queries/questions/PE002_1.rq
diff --git a/queries/questions/PE002_2.rq b/docs/queries/questions/PE002_2.rq
similarity index 100%
rename from queries/questions/PE002_2.rq
rename to docs/queries/questions/PE002_2.rq
diff --git a/queries/questions/REG001.rq b/docs/queries/questions/REG001.rq
similarity index 100%
rename from queries/questions/REG001.rq
rename to docs/queries/questions/REG001.rq
diff --git a/queries/questions/REG002_1.rq b/docs/queries/questions/REG002_1.rq
similarity index 100%
rename from queries/questions/REG002_1.rq
rename to docs/queries/questions/REG002_1.rq
diff --git a/queries/questions/REG002_2.rq b/docs/queries/questions/REG002_2.rq
similarity index 100%
rename from queries/questions/REG002_2.rq
rename to docs/queries/questions/REG002_2.rq
diff --git a/queries/questions/REP001.rq b/docs/queries/questions/REP001.rq
similarity index 100%
rename from queries/questions/REP001.rq
rename to docs/queries/questions/REP001.rq
diff --git a/queries/questions/REP002_1.rq b/docs/queries/questions/REP002_1.rq
similarity index 100%
rename from queries/questions/REP002_1.rq
rename to docs/queries/questions/REP002_1.rq
diff --git a/queries/questions/REP002_2.rq b/docs/queries/questions/REP002_2.rq
similarity index 100%
rename from queries/questions/REP002_2.rq
rename to docs/queries/questions/REP002_2.rq
diff --git a/queries/questions/RP001.rq b/docs/queries/questions/RP001.rq
similarity index 100%
rename from queries/questions/RP001.rq
rename to docs/queries/questions/RP001.rq
diff --git a/queries/questions/RP002_1.rq b/docs/queries/questions/RP002_1.rq
similarity index 100%
rename from queries/questions/RP002_1.rq
rename to docs/queries/questions/RP002_1.rq
diff --git a/queries/questions/RP002_2.rq b/docs/queries/questions/RP002_2.rq
similarity index 100%
rename from queries/questions/RP002_2.rq
rename to docs/queries/questions/RP002_2.rq
diff --git a/queries/questions/SC001.rq b/docs/queries/questions/SC001.rq
similarity index 100%
rename from queries/questions/SC001.rq
rename to docs/queries/questions/SC001.rq
diff --git a/queries/questions/SC002_1.rq b/docs/queries/questions/SC002_1.rq
similarity index 100%
rename from queries/questions/SC002_1.rq
rename to docs/queries/questions/SC002_1.rq
diff --git a/queries/questions/SC002_2.rq b/docs/queries/questions/SC002_2.rq
similarity index 100%
rename from queries/questions/SC002_2.rq
rename to docs/queries/questions/SC002_2.rq
diff --git a/queries/questions/TY001.rq b/docs/queries/questions/TY001.rq
similarity index 100%
rename from queries/questions/TY001.rq
rename to docs/queries/questions/TY001.rq
diff --git a/queries/questions/old/DR001_1.rq b/docs/queries/questions/old/DR001_1.rq
similarity index 100%
rename from queries/questions/old/DR001_1.rq
rename to docs/queries/questions/old/DR001_1.rq
diff --git a/queries/questions/old/OR001_1.rq b/docs/queries/questions/old/OR001_1.rq
similarity index 100%
rename from queries/questions/old/OR001_1.rq
rename to docs/queries/questions/old/OR001_1.rq
diff --git a/queries/questions/old/OR001_2.rq b/docs/queries/questions/old/OR001_2.rq
similarity index 100%
rename from queries/questions/old/OR001_2.rq
rename to docs/queries/questions/old/OR001_2.rq
diff --git a/queries/questions/old/OR002_1.rq b/docs/queries/questions/old/OR002_1.rq
similarity index 100%
rename from queries/questions/old/OR002_1.rq
rename to docs/queries/questions/old/OR002_1.rq
diff --git a/queries/questions/old/OR003_1.rq b/docs/queries/questions/old/OR003_1.rq
similarity index 100%
rename from queries/questions/old/OR003_1.rq
rename to docs/queries/questions/old/OR003_1.rq
diff --git a/queries/questions/old/OR004_1.rq b/docs/queries/questions/old/OR004_1.rq
similarity index 100%
rename from queries/questions/old/OR004_1.rq
rename to docs/queries/questions/old/OR004_1.rq
diff --git a/queries/questions/old/OR005_1.rq b/docs/queries/questions/old/OR005_1.rq
similarity index 100%
rename from queries/questions/old/OR005_1.rq
rename to docs/queries/questions/old/OR005_1.rq
diff --git a/queries/questions/old/OR006_1.rq b/docs/queries/questions/old/OR006_1.rq
similarity index 100%
rename from queries/questions/old/OR006_1.rq
rename to docs/queries/questions/old/OR006_1.rq
diff --git a/scripts/kg_analysis/metrics_runner.py b/scripts/kg_analysis/metrics_runner.py
index 9e39983..efb2854 100644
--- a/scripts/kg_analysis/metrics_runner.py
+++ b/scripts/kg_analysis/metrics_runner.py
@@ -39,7 +39,7 @@ class MetricsRunnerBase(ABC):
 
     def __init__(self, output_dir=None):
         self.runner = QueryRunner()
-        self.base_query_path = Path("queries/metrics")
+        self.base_query_path = Path("metrics")
         self.base_output_path = Path(output_dir) if output_dir else REPORTS_DIR
         self.base_output_path.mkdir(parents=True, exist_ok=True)
         log.info(f"Running metric: {self.__class__.__name__}")
-- 
GitLab


From a3b07d58edfe5f22b76e263044b8b534fd6b0784 Mon Sep 17 00:00:00 2001
From: Ralf Klammer <ralf.klammer@tu-dresden.de>
Date: Thu, 20 Mar 2025 15:07:00 +0100
Subject: [PATCH 47/59] [metrics] Put questions.md in position

---
 docs/index.md     |   2 +-
 docs/questions.md | 170 ++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 171 insertions(+), 1 deletion(-)
 create mode 100644 docs/questions.md

diff --git a/docs/index.md b/docs/index.md
index 5d30e05..31f12d7 100644
--- a/docs/index.md
+++ b/docs/index.md
@@ -1,4 +1,4 @@
-# {{ config.site_name }}
+# Overview
 
 Welcome to the NFDI4Earth KnowledgeGraph Query Collection. This documentation provides insights into the NFDI4Earth KnowledgeHub Graph through SPARQL queries and their analysis.
 
diff --git a/docs/questions.md b/docs/questions.md
new file mode 100644
index 0000000..c712676
--- /dev/null
+++ b/docs/questions.md
@@ -0,0 +1,170 @@
+# Questions
+
+This is a collection of relevant questions and corresponding SPARQL-Queries, that answer those questions.
+
+The questions are grouped according to the different entities of interest
+(datasets, organizations, ...).
+
+The entities appear in alphabetical order. The first query is useful to get an overview of all entities available in the Knowledge Hub.
+
+The questions listed below form, altogether, the **domain coverage** of the Knowledge Hub. For details, see the [NFDI4Earth Deliverable D4.3.2](https://zenodo.org/records/7950860).
+
+## Overview of the types of entities
+
+| ID    | Question | Query/ies |
+|---|---|---|
+| TY001	| What are the types of entities available in the knowledge graph? | [TY001](queries/questions/TY001.rq)|
+
+&nbsp;
+&nbsp;
+
+### Aggregator
+
+| ID    | Question | Query/ies |
+|---|---|---|
+| AG001	| What are all entities of type Aggregator? | [AG001](queries/questions/AG001.rq)|
+| AG001_2	| What are name and geometry of Aggregator? | [AG001_2](queries/questions/AG001_2.rq)|
+| AG002_1	| What are all attributes available for the type "Aggregator"? | [AG002_1](queries/questions/AG002_1.rq)|
+| AG002_2	| How many attributes are available for the type "Aggregator"? | [AG002_2](queries/questions/AG002_2.rq)|
+
+### Article
+
+| ID    | Question | Query/ies |
+|---|---|---|
+| AT001	| What are all entities of type schema:Article? | [AT001](queries/questions/AT001.rq)|
+| AT002_1	| What are all attributes available for the type "schema:Article"? | [AT002_1](queries/questions/AT002_1.rq)|
+| AT002_2	| How many attributes are available for the type "schema:Article"? | [AT002_2](queries/questions/AT002_2.rq)|
+
+### Dataset
+
+| ID    | Question | Query/ies |
+|---|---|---|
+| DA001	| What are all entities of type dcat:Dataset? | [DA001](queries/questions/DA001.rq)|
+| DA002_1	| What are all attributes available for the type "dcat:Dataset"? | [DA002_1](queries/questions/DA002_1.rq)|
+| DA002_2	| How many attributes are available for the type "dcat:Dataset"? | [DA002_2](queries/questions/DA002_2.rq)|
+| DA003_1	| What are the datasets having the string 'world settlement footprint' in title or description? | [DA003_1](queries/questions/DA003_1.rq)|
+
+### LHBArticle
+
+| ID    | Question | Query/ies |
+|---|---|---|
+| LH001	| What are all entities of type LHBArticle? | [LH001](queries/questions/LH001.rq)|
+| LH002_1	| What are all attributes available for the type "LHBArticle"? | [LH002_1](queries/questions/LH002_1.rq)|
+| LH002_2	| How many attributes are available for the type "LHBArticle"? | [LH002_2](queries/questions/LH002_2.rq)|
+
+### LearningResource
+
+| ID    | Question | Query/ies |
+|---|---|---|
+| LR001	| What are all entities of type LearningResource? | [LR001](queries/questions/LR001.rq)|
+| LR002_1	| What are all attributes available for the type "LearningResource"? | [LR002_1](queries/questions/LR002_1.rq)|
+| LR002_2	| How many attributes are available for the type "LearningResource"? | [LR002_2](queries/questions/LR002_2.rq)|
+
+### MetadataStandard
+
+| ID    | Question | Query/ies |
+|---|---|---|
+| MS001	| What are all entities of type MetadataStandard? | [MS001](queries/questions/MS001.rq)|
+| MS002_1	| What are all attributes available for the type "MetadataStandard"? | [MS002_1](queries/questions/MS002_1.rq)|
+| MS002_2	| How many attributes are available for the type "MetadataStandard"? | [MS002_2](queries/questions/MS002_2.rq)|
+
+### Organization
+
+| ID    | Question | Query/ies |
+|---|---|---|
+| OG001	| What are all entities of type Organization? | [OG001](queries/questions/OG001.rq)|
+| OG002_1	| What are all attributes available for the type "Organization"? | [OG002_1](queries/questions/OG002_1.rq)|
+| OG002_2	| How many attributes are available for the type "Organization"? | [OG002_2](queries/questions/OG002_2.rq)|
+
+### Person
+
+| ID    | Question | Query/ies |
+|---|---|---|
+| PE001	| What are all entities of type Person? | [PE001](queries/questions/PE001.rq)|
+| PE002_1	| What are all attributes available for the type "Person"? | [PE002_1](queries/questions/PE002_1.rq)|
+| PE002_2	| How many attributes are available for the type "Person"? | [PE002_2](queries/questions/PE002_2.rq)|
+
+### Registry
+
+| ID    | Question | Query/ies |
+|---|---|---|
+| REG001	| What are all entities of type Registry? | [REG001](queries/questions/REG001.rq)|
+| REG002_1	| What are all attributes available for the type "Registry"? | [REG002_1](queries/questions/REG002_1.rq)|
+| REG002_2	| How many attributes are available for the type "Registry"? | [REG002_2](queries/questions/REG002_2.rq)|
+
+### Repository
+
+| ID    | Question | Query/ies |
+|---|---|---|
+| REP001	| What are all entities of type Repository? | [REP001](queries/questions/REP001.rq)|
+| REP002_1	| What are all attributes available for the type "Repository"? | [REP002_1](queries/questions/REP002_1.rq)|
+| REP002_2	| How many attributes are available for the type "Repository"? | [REP002_2](queries/questions/REP002_2.rq)|
+
+### ResearchProject
+
+| ID    | Question | Query/ies |
+|---|---|---|
+| RP001	| What are all entities of type ResearchProject? | [RP001](queries/questions/RP001.rq)|
+| RP002_1	| What are all attributes available for the type "ResearchProject"? | [RP002_1](queries/questions/RP002_1.rq)|
+| RP002_2	| How many attributes are available for the type "ResearchProject"? | [RP002_2](queries/questions/RP002_2.rq)|
+
+
+### SoftwareSourceCode
+
+| ID    | Question | Query/ies |
+|---|---|---|
+| SC001	| What are all entities of type SoftwareSourceCode? | [SC001](queries/questions/SC001.rq)|
+| SC002_1	| What are all attributes available for the type "SoftwareSourceCode"? | [SC002_1](queries/questions/SC002_1.rq)|
+| SC002_2	| How many attributes are available for the type "SoftwareSourceCode"? | [SC002_2](queries/questions/SC002_2.rq)|
+
+
+<!--- Template for a new table (including first line)
+
+### EntityType
+
+| ID    | Question | Query/ies |
+|---|---|---|
+| XX001	| What are all entities of type EntityType? | [XX001](queries/questions/XX001.rq)|
+
+-->
+
+
+<!---
+
+
+### Organizations
+
+| ID    | Question | Query/ies |
+|---|---|---|
+| OR001	| What is the URL of the homepage for the organization with the following name: 'Karlsruhe Institute of Technology'? | [OR001_1](queries/questions/OR001_1.rq),[OR001_2](queries/questions/OR001_2.rq) |
+| OR002 | What is the URL of the homepage for the organization with the following ID: 'https://nfdi4earth-knowledgehub.geo.tu-dresden.de/api/objects/n4ekh/a38143be5e15bed94a20' | [OR003_1](queries/questions/OR003_1.rq) |
+| OR003 | Which organizations have not defined any homepage? | [OR003_1](queries/questions/OR003_1.rq) |
+| OR004 | Which services are published by the organization? | [OR004_1](queries/questions/OR004_1.rq) |
+| OR005 | What is the geolocation of the organization called 'TU Dresden'? | [OR005_1](queries/questions/OR005_1.rq) |
+| OR006 | What is the geolocation of all organizations, that are members of the NFDI4Earth consortium? | [OR006_1](queries/questions/OR006_1.rq) |
+
+### Repositories
+
+| ID | Question | Query/ies |
+|----|----------|-----------|
+| DR1 | At which repository can I archive my [geophysical] data of [2] GB?| [OR004_1](queries/questions/OR004_1.rq) |
+| DR2 | What is the temporal coverage of a data repository?||
+| DR3 | What is the spatial coverage of a data repository?||
+| DR4 | What is the curation policy of the data repository?||
+| DR5 | Which licences are supported by the data repository?||
+| DR6 | Does the repository give identifiers for its ressources?||
+| DR7 | Which metadata harversting interface is supported by the repository?||
+| DR8 | Which type of (persistent) identifiers are used by the repository?||
+| DR9 | What is the thematic area/subject of a repository?||
+| DR10 | Limitations of data deposit at the repository?||
+| DR11 | When was the medatada for a given repository first collected/last updated?||
+| DR12 | Is the repository still available?||
+| DR13 | Which repository allows long term archiving?||
+
+-->
+
+## Notes
+
+This question-based approach takes inspiration from the [GeoSPARQLBenchmark](https://github.com/OpenLinkSoftware/GeoSPARQLBenchmark).
+
+It is directly linked to the [Knowledge Hub landing page project](https://git.rwth-aachen.de/nfdi4earth/knowledgehub/kh_landingpage) as all the questions are taken to explain the basic idea and demonstrate usage of the [Knowledge Hub](https://knowledgehub.nfdi4earth.de).
-- 
GitLab


From 07b4ff22f09d713bd5b005ac77dedf0697f8d537 Mon Sep 17 00:00:00 2001
From: Ralf Klammer <ralf.klammer@tu-dresden.de>
Date: Thu, 20 Mar 2025 15:08:19 +0100
Subject: [PATCH 48/59] [metrics]fix: links to queries

---
 docs/macros/resource_metrics.md               | 24 ++++++++--------
 docs/metrics/general metrics/01_instances.md  |  4 ++-
 docs/metrics/general metrics/02_assertions.md | 18 ++++++------
 .../general metrics/03_linkage_degree.md      | 24 ++++++++--------
 .../general metrics/04_edges_incoming.md      | 22 ++++++++++-----
 .../general metrics/05_edges_outgoing.md      | 28 ++++++++++---------
 .../schema_complexity_metrics/01_classes.md   |  2 +-
 .../02_properties.md                          | 12 ++++----
 .../schema_complexity_metrics/03_depth.md     |  2 +-
 .../schema_complexity_metrics/04_width.md     |  2 +-
 .../05_restrictions.md                        |  2 +-
 .../schema_complexity_metrics/06_axioms.md    |  2 +-
 scripts/kg_analysis/table_renderer.py         | 23 ++++++++++++---
 13 files changed, 96 insertions(+), 69 deletions(-)

diff --git a/docs/macros/resource_metrics.md b/docs/macros/resource_metrics.md
index de24e0c..ba82029 100644
--- a/docs/macros/resource_metrics.md
+++ b/docs/macros/resource_metrics.md
@@ -14,14 +14,14 @@ see also: [general_metricsinstances](/metrics/general%20metrics/01_instances/)
 <i id="RM001_instances_template.rq">file: RM001_instances_template.rq</i>
 
 ```sparql
-{{ include_template("queries/metrics/RM001_instances_template.rq", resource_type_uri) }}
+{{ include_template("metrics/RM001_instances_template.rq", resource_type_uri) }}
 ```
 ### Connectivity to other resources
 
 <i id="RM006_connectivity_template.rq">file: RM006_connectivity_template.rq</i>
 
 ```sparql
-{{ include_template("queries/metrics/RM006_connectivity_template.rq", resource_type_uri) }}
+{{ include_template("metrics/RM006_connectivity_template.rq", resource_type_uri) }}
 ```
 
 ### Number of Assertions
@@ -31,7 +31,7 @@ see also: [general_metricsassortions](/metrics/general%20metrics/02_assertions/)
 <i id="RM002_assertions_template.rq">file: RM002_assertions_template.rq</i>
 
 ```sparql
-{{ include_template("queries/metrics/RM002_assertions_template.rq", resource_type_uri) }}
+{{ include_template("metrics/RM002_assertions_template.rq", resource_type_uri) }}
 ```
 
 ### Average linkage
@@ -41,7 +41,7 @@ see also: [general_metricslinkage](/metrics/general%20metrics/03_linkage_degree/
 <i id="RM003_linkage_template.rq">file: RM003_linkage_template.rq</i>
 
 ```sparql
-{{ include_template("queries/metrics/RM003_linkage_template.rq", resource_type_uri) }}
+{{ include_template("metrics/RM003_linkage_template.rq", resource_type_uri) }}
 ```
 
 ### Outgoing Edges Statistics
@@ -53,7 +53,7 @@ see also: [general_metricsoutgoing edges](/metrics/general%20metrics/04_outgoing
 <i id="RM004_1_out_edges_total_template.rq">file: RM004_1_out_edges_total_template.rq</i>
 
 ```sparql
-{{ include_template("queries/metrics/RM004_1_out_edges_total_template.rq", resource_type_uri) }}
+{{ include_template("metrics/RM004_1_out_edges_total_template.rq", resource_type_uri) }}
 ```
 
 #### Minimum outgoing edges
@@ -61,7 +61,7 @@ see also: [general_metricsoutgoing edges](/metrics/general%20metrics/04_outgoing
 <i id="RM004_2_out_edges_min_template.rq">file: RM004_2_out_edges_min_template.rq</i>
 
 ```sparql
-{{ include_template("queries/metrics/RM004_2_out_edges_min_template.rq", resource_type_uri) }}
+{{ include_template("metrics/RM004_2_out_edges_min_template.rq", resource_type_uri) }}
 ```
 
 #### Median outgoing edges
@@ -69,7 +69,7 @@ see also: [general_metricsoutgoing edges](/metrics/general%20metrics/04_outgoing
 <i id="RM004_3_out_edges_median_template.rq">file: RM004_3_out_edges_median_template.rq</i>
 
 ```sparql
-{{ include_template("queries/metrics/RM004_3_out_edges_median_template.rq", resource_type_uri) }}
+{{ include_template("metrics/RM004_3_out_edges_median_template.rq", resource_type_uri) }}
 ```
 
 #### Maximum outgoing edges
@@ -77,7 +77,7 @@ see also: [general_metricsoutgoing edges](/metrics/general%20metrics/04_outgoing
 <i id="RM004_4_out_edges_max_template.rq">file: RM004_4_out_edges_max_template.rq</i>
 
 ```sparql
-{{ include_template("queries/metrics/RM004_4_out_edges_max_template.rq", resource_type_uri) }}
+{{ include_template("metrics/RM004_4_out_edges_max_template.rq", resource_type_uri) }}
 ```
 
 ### Incoming Edges Statistics
@@ -89,7 +89,7 @@ see also: [general_metricsincoming edges](/metrics/general%20metrics/05_incoming
 <i id="RM005_1_in_edges_total_template.rq">file: RM005_1_in_edges_total_template.rq</i>
 
 ```sparql
-{{ include_template("queries/metrics/RM005_1_in_edges_total_template.rq", resource_type_uri) }}
+{{ include_template("metrics/RM005_1_in_edges_total_template.rq", resource_type_uri) }}
 ```
 
 #### Minimum incoming edges
@@ -97,7 +97,7 @@ see also: [general_metricsincoming edges](/metrics/general%20metrics/05_incoming
 <i id="RM005_2_in_edges_min_template.rq">file: RM005_2_in_edges_min_template.rq</i>
 
 ```sparql
-{{ include_template("queries/metrics/RM005_2_in_edges_min_template.rq", resource_type_uri) }}
+{{ include_template("metrics/RM005_2_in_edges_min_template.rq", resource_type_uri) }}
 ```
 
 #### Median incoming edges
@@ -105,7 +105,7 @@ see also: [general_metricsincoming edges](/metrics/general%20metrics/05_incoming
 <i id="RM005_3_in_edges_median_template.rq">file: RM005_3_in_edges_median_template.rq</i>
 
 ```sparql
-{{ include_template("queries/metrics/RM005_3_in_edges_median_template.rq", resource_type_uri) }}
+{{ include_template("metrics/RM005_3_in_edges_median_template.rq", resource_type_uri) }}
 ```
 
 #### Maximum incoming edges
@@ -113,7 +113,7 @@ see also: [general_metricsincoming edges](/metrics/general%20metrics/05_incoming
 <i id="RM005_4_in_edges_max_template.rq">file: RM005_4_in_edges_max_template.rq</i>
 
 ```sparql
-{{ include_template("queries/metrics/RM005_4_in_edges_max_template.rq", resource_type_uri) }}
+{{ include_template("metrics/RM005_4_in_edges_max_template.rq", resource_type_uri) }}
 ```
 
 {% endmacro %}
diff --git a/docs/metrics/general metrics/01_instances.md b/docs/metrics/general metrics/01_instances.md
index e37a978..57ae0b3 100644
--- a/docs/metrics/general metrics/01_instances.md	
+++ b/docs/metrics/general metrics/01_instances.md	
@@ -15,6 +15,8 @@ The metric helps to:
 
 ### Count Number of instances in a graph
 
+<i id="GM001.rq">file: GM001.rq</i>
+
 ```sparql
-{{ include_if_exists("queries/metrics/GM001.rq") }}
+{{ include_if_exists("metrics/GM001.rq") }}
 ```
diff --git a/docs/metrics/general metrics/02_assertions.md b/docs/metrics/general metrics/02_assertions.md
index 11d911d..a3d1363 100644
--- a/docs/metrics/general metrics/02_assertions.md	
+++ b/docs/metrics/general metrics/02_assertions.md	
@@ -15,24 +15,24 @@ The metric helps to:
 
 ### Count Total Number of Assertions
 
+<i id="GM002_1.rq">file: GM002_1.rq</i>
+
 ```sparql
-{{ include_if_exists("queries/metrics/GM002_1.rq") }}
+{{ include_if_exists("metrics/GM002_1.rq") }}
 ```
 
 ### Count Number of Entity-to-Entity Assertions
 
+<i id="GM002_2.rq">file: GM002_2.rq</i>
+
 ```sparql
-{{ include_if_exists("queries/metrics/GM002_2.rq") }}
+{{ include_if_exists("metrics/GM002_2.rq") }}
 ```
 
 ### Count Number of Entity-to-Literal Assertions
 
+<i id="GM002_3.rq">file: GM002_3.rq</i>
+
 ```sparql
-{{ include_if_exists("queries/metrics/GM002_3.rq") }}
+{{ include_if_exists("metrics/GM002_3.rq") }}
 ```
-
-## Results
-{{ include_if_exists("reports/metrics/GM002.txt", start_line=1) }}
-
-Last execution:
-{{ include_if_exists("reports/metrics/GM002.txt", single_line=0) }}
\ No newline at end of file
diff --git a/docs/metrics/general metrics/03_linkage_degree.md b/docs/metrics/general metrics/03_linkage_degree.md
index e640cf4..defdff0 100644
--- a/docs/metrics/general metrics/03_linkage_degree.md	
+++ b/docs/metrics/general metrics/03_linkage_degree.md	
@@ -17,8 +17,10 @@ The metric provides insights into:
 
 This query calculates the average number of relationships (both incoming and outgoing) per entity in the graph.
 
+<i id="GM003_1.rq">file: GM003_1.rq</i>
+
 ```sparql
-{{ include_if_exists("queries/metrics/GM003_1.rq") }}
+{{ include_if_exists("metrics/GM003_1.rq") }}
 ```
 
 ??? warning "Batch Processing Limitation"
@@ -30,30 +32,28 @@ This query calculates the average number of relationships (both incoming and out
 
 This is an optimized version of the linkage degree calculation that uses batch processing. It's particularly useful for large datasets where the standard query might timeout or consume too many resources. The query processes a limited number of entities at a time.
 
+<i id="GM003_2.rq">file: GM003_2.rq</i>
+
 ```sparql
-{{ include_if_exists("queries/metrics/GM003_2.rq") }}
+{{ include_if_exists("metrics/GM003_2.rq") }}
 ```
 
 ### Average Outgoing Linkage Degree (Batch Processing)
 
 This query focuses specifically on outgoing relationships, calculating the average number of outgoing edges per entity. It uses batch processing for efficient execution on larger datasets.
 
+<i id="GM003_3.rq">file: GM003_3.rq</i>
+
 ```sparql
-{{ include_if_exists("queries/metrics/GM003_3.rq") }}
+{{ include_if_exists("metrics/GM003_3.rq") }}
 ```
 
 ### Average Incoming Linkage Degree (Batch Processing)
 
 This query calculates the average number of incoming relationships per entity, providing insights into how frequently entities are referenced by others in the graph. It also uses batch processing for efficiency.
 
+<i id="GM003_4.rq">file: GM003_4.rq</i>
+
 ```sparql
-{{ include_if_exists("queries/metrics/GM003_4.rq") }}
+{{ include_if_exists("metrics/GM003_4.rq") }}
 ```
-
-## Results
-
-### Average Linkage Degree
-{{ include_if_exists("reports/metrics/GM003.txt", start_line=1) }}
-
-Last execution:
-{{ include_if_exists("reports/metrics/GM003.txt", single_line=0) }}
\ No newline at end of file
diff --git a/docs/metrics/general metrics/04_edges_incoming.md b/docs/metrics/general metrics/04_edges_incoming.md
index 899f5ea..d649181 100644
--- a/docs/metrics/general metrics/04_edges_incoming.md	
+++ b/docs/metrics/general metrics/04_edges_incoming.md	
@@ -9,13 +9,15 @@ This metric determines the median number of incoming edges across all nodes in t
 ### Step 1: Count Incoming Edges Per Node
 
 ```sparql
-{{ include_if_exists("queries/metrics/GM005_1.rq") }}
+{{ include_if_exists("metrics/GM005_1.rq") }}
 ```
 
 ### Step 2: Get Total Number of Nodes
 
+<i id="GM005_2.rq">file: GM005_2.rq</i>
+
 ```sparql
-{{ include_if_exists("queries/metrics/GM005_2.rq") }}
+{{ include_if_exists("metrics/GM005_2.rq") }}
 ```
 
 ### Step 3: Calculate Median Position
@@ -23,23 +25,29 @@ This metric determines the median number of incoming edges across all nodes in t
 Using the total node count (n), median position is: position = (n+1)/2
 
 ```sparql
-{{ include_if_exists("queries/metrics/GM005_3.rq") }}
+{{ include_if_exists("metrics/GM005_3.rq") }}
 ```
 
 ### Step 4: Get Median Value
 
+<i id="GM005_4.rq">file: GM005_4.rq</i>
+
 ```sparql
-{{ include_if_exists("queries/metrics/GM005_4.rq") }}
+{{ include_if_exists("metrics/GM005_4.rq") }}
 ```
 
 ### Step 5: Get Minimum Value
 
+<i id="GM005_5.rq">file: GM005_5.rq</i>
+
 ```sparql
-{{ include_if_exists("queries/metrics/GM005_5.rq") }}
+{{ include_if_exists("metrics/GM005_5.rq") }}
 ```
 
-### Step 5: Get Maximum Value
+### Step 6: Get Maximum Value
+
+<i id="GM005_6.rq">file: GM005_6.rq</i>
 
 ```sparql
-{{ include_if_exists("queries/metrics/GM005_6.rq") }}
+{{ include_if_exists("metrics/GM005_6.rq") }}
 ```
diff --git a/docs/metrics/general metrics/05_edges_outgoing.md b/docs/metrics/general metrics/05_edges_outgoing.md
index 2ecec14..5531d38 100644
--- a/docs/metrics/general metrics/05_edges_outgoing.md	
+++ b/docs/metrics/general metrics/05_edges_outgoing.md	
@@ -9,13 +9,15 @@ This metric determines the median number of outgoing edges across all nodes in t
 ### Step 1: Count Outgoing Edges Per Node
 
 ```sparql
-{{ include_if_exists("queries/metrics/GM004_1.rq") }}
+{{ include_if_exists("metrics/GM004_1.rq") }}
 ```
 
 ### Step 2: Get Total Number of Nodes
 
+<i id="GM004_2.rq">file: GM004_2.rq</i>
+
 ```sparql
-{{ include_if_exists("queries/metrics/GM004_2.rq") }}
+{{ include_if_exists("metrics/GM004_2.rq") }}
 ```
 
 ### Step 3: Calculate Median Position
@@ -23,29 +25,29 @@ This metric determines the median number of outgoing edges across all nodes in t
 Using the total node count (n), median position is: position = (n+1)/2
 
 ```sparql
-{{ include_if_exists("queries/metrics/GM004_3.rq") }}
+{{ include_if_exists("metrics/GM004_3.rq") }}
 ```
 
 ### Step 4: Get Median Value(s)
 
+<i id="GM004_4.rq">file: GM004_4.rq</i>
+
 ```sparql
-{{ include_if_exists("queries/metrics/GM004_4.rq") }}
+{{ include_if_exists("metrics/GM004_4.rq") }}
 ```
 
 ### Step 5: Get Minimum Value
 
+<i id="GM004_5.rq">file: GM004_5.rq</i>
+
 ```sparql
-{{ include_if_exists("queries/metrics/GM004_5.rq") }}
+{{ include_if_exists("metrics/GM004_5.rq") }}
 ```
 
-### Step 5: Get Maximum Value
+### Step 6: Get Maximum Value
+
+<i id="GM004_6.rq">file: GM004_6.rq</i>
 
 ```sparql
-{{ include_if_exists("queries/metrics/GM004_6.rq") }}
+{{ include_if_exists("metrics/GM004_6.rq") }}
 ```
-
-## Results
-{{ include_if_exists("reports/metrics/GM004.txt", start_line=1) }}
-
-Last execution:
-{{ include_if_exists("reports/metrics/GM004.txt", single_line=0) }}
\ No newline at end of file
diff --git a/docs/metrics/schema_complexity_metrics/01_classes.md b/docs/metrics/schema_complexity_metrics/01_classes.md
index 953bbaf..c1f3b96 100644
--- a/docs/metrics/schema_complexity_metrics/01_classes.md
+++ b/docs/metrics/schema_complexity_metrics/01_classes.md
@@ -9,7 +9,7 @@ This metric counts the total number of classes defined in our schema.
 ## SPARQL Query
 
 ```sparql
-{{ include_if_exists("queries/metrics/RF_001.rq") }}
+{{ include_if_exists("metrics/RF_001.rq") }}
 ```
 
 ## Description
diff --git a/docs/metrics/schema_complexity_metrics/02_properties.md b/docs/metrics/schema_complexity_metrics/02_properties.md
index a703c88..a052801 100644
--- a/docs/metrics/schema_complexity_metrics/02_properties.md
+++ b/docs/metrics/schema_complexity_metrics/02_properties.md
@@ -9,37 +9,37 @@ This metric analyzes the properties defined in our schema through multiple aspec
 ## Total Properties Count
 
 ```sparql
-{{ include_if_exists("queries/metrics/RF_002_1.rq") }}
+{{ include_if_exists("metrics/RF_002_1.rq") }}
 ```
 
 ## Object Properties Count
 
 ```sparql
-{{ include_if_exists("queries/metrics/RF_002_2.rq") }}
+{{ include_if_exists("metrics/RF_002_2.rq") }}
 ```
 
 ## Datatype Properties Count
 
 ```sparql
-{{ include_if_exists("queries/metrics/RF_002_3.rq") }}
+{{ include_if_exists("metrics/RF_002_3.rq") }}
 ```
 
 ## Properties with Domain
 
 ```sparql
-{{ include_if_exists("queries/metrics/RF_002_4.rq") }}
+{{ include_if_exists("metrics/RF_002_4.rq") }}
 ```
 
 ## Properties with Range
 
 ```sparql
-{{ include_if_exists("queries/metrics/RF_002_5.rq") }}
+{{ include_if_exists("metrics/RF_002_5.rq") }}
 ```
 
 ## Combined Metrics Query
 
 ```sparql
-{{ include_if_exists("queries/metrics/RF_002_6.rq") }}
+{{ include_if_exists("metrics/RF_002_6.rq") }}
 ```
 
 ## Interpretation
diff --git a/docs/metrics/schema_complexity_metrics/03_depth.md b/docs/metrics/schema_complexity_metrics/03_depth.md
index 671be88..4602061 100644
--- a/docs/metrics/schema_complexity_metrics/03_depth.md
+++ b/docs/metrics/schema_complexity_metrics/03_depth.md
@@ -10,7 +10,7 @@ This metric calculates the average depth of the class hierarchy in the schema.
 ## SPARQL Query
 
 ```sparql
-{{ include_if_exists("queries/metrics/RF_003.rq") }}
+{{ include_if_exists("metrics/RF_003.rq") }}
 ```
 
 ## Description
diff --git a/docs/metrics/schema_complexity_metrics/04_width.md b/docs/metrics/schema_complexity_metrics/04_width.md
index 5acfb26..3d07f42 100644
--- a/docs/metrics/schema_complexity_metrics/04_width.md
+++ b/docs/metrics/schema_complexity_metrics/04_width.md
@@ -9,7 +9,7 @@ This metric calculates the average number of subclasses per class (branching fac
 ## SPARQL Query
 
 ```sparql
-{{ include_if_exists("queries/metrics/RF_004.rq") }}
+{{ include_if_exists("metrics/RF_004.rq") }}
 ```
 
 ## Description
diff --git a/docs/metrics/schema_complexity_metrics/05_restrictions.md b/docs/metrics/schema_complexity_metrics/05_restrictions.md
index 2c68bc9..7fa5d5e 100644
--- a/docs/metrics/schema_complexity_metrics/05_restrictions.md
+++ b/docs/metrics/schema_complexity_metrics/05_restrictions.md
@@ -9,7 +9,7 @@ This metric counts the various types of OWL restrictions defined in the schema.
 ## SPARQL Query
 
 ```sparql
-{{ include_if_exists("queries/metrics/RF_006.rq") }}
+{{ include_if_exists("metrics/RF_006.rq") }}
 ```
 
 ## Description
diff --git a/docs/metrics/schema_complexity_metrics/06_axioms.md b/docs/metrics/schema_complexity_metrics/06_axioms.md
index 53734f6..66846df 100644
--- a/docs/metrics/schema_complexity_metrics/06_axioms.md
+++ b/docs/metrics/schema_complexity_metrics/06_axioms.md
@@ -9,7 +9,7 @@ This metric counts the various types of OWL logical axioms defined in the schema
 ## SPARQL Query
 
 ```sparql
-{{ include_if_exists("queries/metrics/RF_007.rq") }}
+{{ include_if_exists("metrics/RF_006.rq") }}
 ```
 
 ## Description
diff --git a/scripts/kg_analysis/table_renderer.py b/scripts/kg_analysis/table_renderer.py
index b2b3e12..38d0d01 100644
--- a/scripts/kg_analysis/table_renderer.py
+++ b/scripts/kg_analysis/table_renderer.py
@@ -92,25 +92,37 @@ class MetricsTableRenderer:
         self.output.append(f"\n*Last updated: {self.timestamp}*\n")
         return "\n".join(self.output)
 
-    def _render_horizontal_table(self, data):
+    def _render_horizontal_table(self, data, add_links=True):
+        """Renders a horizontal table for the given data."""
+        # Create the table header
         self.output.append("| Metric | Query (file) | Result |")
         self.output.append("|--------|------|--------|")
 
         # Iterate over each query data for the specified resource type
         for q_data in data.values():
             if "file" in q_data:
+                filename = (
+                    f"[{q_data['file']}](#{q_data['file']})"
+                    if add_links
+                    else q_data["file"]
+                )
                 row = [
                     q_data["name"],
-                    f"[{q_data['file']}](#{q_data['file']})",
+                    filename,
                     str(q_data["result"]),
                 ]
                 self.output.append("| " + " | ".join(row) + " |")
             elif "files" in q_data:
                 self.output.append(f"| **{q_data['name']}** | | |")
                 for sub_q_data in q_data["files"].values():
+                    filename = (
+                        f"[{sub_q_data['file']}](#{sub_q_data['file']})"
+                        if add_links
+                        else sub_q_data["file"]
+                    )
                     sub_row = [
                         sub_q_data["name"],
-                        f"[{sub_q_data['file']}](#{sub_q_data['file']})",
+                        filename,
                         str(sub_q_data["result"]),
                     ]
                     self.output.append("| " + " | ".join(sub_row) + " |")
@@ -129,7 +141,10 @@ class MetricsTableRenderer:
             if self.metric_key
             else self.data
         )
-        self._render_horizontal_table(data)
+        # Render the horizontal table
+        # Add links to the file names if a metric key is specified,
+        # i.e. the table is for a single metric
+        self._render_horizontal_table(data, add_links=bool(self.metric_key))
 
     def _render_resource_overview(self):
         """Renders the resource overview table."""
-- 
GitLab


From 8c97ba401dbe8c877b3f20ee83f4f43b5c3d18e4 Mon Sep 17 00:00:00 2001
From: Ralf Klammer <ralf.klammer@tu-dresden.de>
Date: Thu, 20 Mar 2025 15:33:50 +0100
Subject: [PATCH 49/59] [metrics] Remove whitespace from folder names

---
 docs/metrics/{general metrics => general_metrics}/01_instances.md | 0
 .../metrics/{general metrics => general_metrics}/02_assertions.md | 0
 .../{general metrics => general_metrics}/03_linkage_degree.md     | 0
 .../{general metrics => general_metrics}/04_edges_incoming.md     | 0
 .../{general metrics => general_metrics}/05_edges_outgoing.md     | 0
 docs/metrics/{resource metrics => resource_metrics}/aggregator.md | 0
 .../metrics/{resource metrics => resource_metrics}/article_lhb.md | 0
 .../{resource metrics => resource_metrics}/data_service.md        | 0
 docs/metrics/{resource metrics => resource_metrics}/dataset.md    | 0
 .../{resource metrics => resource_metrics}/learning_resource.md   | 0
 .../{resource metrics => resource_metrics}/organization.md        | 0
 docs/metrics/{resource metrics => resource_metrics}/person.md     | 0
 .../metrics/{resource metrics => resource_metrics}/publication.md | 0
 docs/metrics/{resource metrics => resource_metrics}/registry.md   | 0
 docs/metrics/{resource metrics => resource_metrics}/repository.md | 0
 docs/metrics/{resource metrics => resource_metrics}/service.md    | 0
 docs/metrics/{resource metrics => resource_metrics}/software.md   | 0
 docs/metrics/{resource metrics => resource_metrics}/standards.md  | 0
 18 files changed, 0 insertions(+), 0 deletions(-)
 rename docs/metrics/{general metrics => general_metrics}/01_instances.md (100%)
 rename docs/metrics/{general metrics => general_metrics}/02_assertions.md (100%)
 rename docs/metrics/{general metrics => general_metrics}/03_linkage_degree.md (100%)
 rename docs/metrics/{general metrics => general_metrics}/04_edges_incoming.md (100%)
 rename docs/metrics/{general metrics => general_metrics}/05_edges_outgoing.md (100%)
 rename docs/metrics/{resource metrics => resource_metrics}/aggregator.md (100%)
 rename docs/metrics/{resource metrics => resource_metrics}/article_lhb.md (100%)
 rename docs/metrics/{resource metrics => resource_metrics}/data_service.md (100%)
 rename docs/metrics/{resource metrics => resource_metrics}/dataset.md (100%)
 rename docs/metrics/{resource metrics => resource_metrics}/learning_resource.md (100%)
 rename docs/metrics/{resource metrics => resource_metrics}/organization.md (100%)
 rename docs/metrics/{resource metrics => resource_metrics}/person.md (100%)
 rename docs/metrics/{resource metrics => resource_metrics}/publication.md (100%)
 rename docs/metrics/{resource metrics => resource_metrics}/registry.md (100%)
 rename docs/metrics/{resource metrics => resource_metrics}/repository.md (100%)
 rename docs/metrics/{resource metrics => resource_metrics}/service.md (100%)
 rename docs/metrics/{resource metrics => resource_metrics}/software.md (100%)
 rename docs/metrics/{resource metrics => resource_metrics}/standards.md (100%)

diff --git a/docs/metrics/general metrics/01_instances.md b/docs/metrics/general_metrics/01_instances.md
similarity index 100%
rename from docs/metrics/general metrics/01_instances.md
rename to docs/metrics/general_metrics/01_instances.md
diff --git a/docs/metrics/general metrics/02_assertions.md b/docs/metrics/general_metrics/02_assertions.md
similarity index 100%
rename from docs/metrics/general metrics/02_assertions.md
rename to docs/metrics/general_metrics/02_assertions.md
diff --git a/docs/metrics/general metrics/03_linkage_degree.md b/docs/metrics/general_metrics/03_linkage_degree.md
similarity index 100%
rename from docs/metrics/general metrics/03_linkage_degree.md
rename to docs/metrics/general_metrics/03_linkage_degree.md
diff --git a/docs/metrics/general metrics/04_edges_incoming.md b/docs/metrics/general_metrics/04_edges_incoming.md
similarity index 100%
rename from docs/metrics/general metrics/04_edges_incoming.md
rename to docs/metrics/general_metrics/04_edges_incoming.md
diff --git a/docs/metrics/general metrics/05_edges_outgoing.md b/docs/metrics/general_metrics/05_edges_outgoing.md
similarity index 100%
rename from docs/metrics/general metrics/05_edges_outgoing.md
rename to docs/metrics/general_metrics/05_edges_outgoing.md
diff --git a/docs/metrics/resource metrics/aggregator.md b/docs/metrics/resource_metrics/aggregator.md
similarity index 100%
rename from docs/metrics/resource metrics/aggregator.md
rename to docs/metrics/resource_metrics/aggregator.md
diff --git a/docs/metrics/resource metrics/article_lhb.md b/docs/metrics/resource_metrics/article_lhb.md
similarity index 100%
rename from docs/metrics/resource metrics/article_lhb.md
rename to docs/metrics/resource_metrics/article_lhb.md
diff --git a/docs/metrics/resource metrics/data_service.md b/docs/metrics/resource_metrics/data_service.md
similarity index 100%
rename from docs/metrics/resource metrics/data_service.md
rename to docs/metrics/resource_metrics/data_service.md
diff --git a/docs/metrics/resource metrics/dataset.md b/docs/metrics/resource_metrics/dataset.md
similarity index 100%
rename from docs/metrics/resource metrics/dataset.md
rename to docs/metrics/resource_metrics/dataset.md
diff --git a/docs/metrics/resource metrics/learning_resource.md b/docs/metrics/resource_metrics/learning_resource.md
similarity index 100%
rename from docs/metrics/resource metrics/learning_resource.md
rename to docs/metrics/resource_metrics/learning_resource.md
diff --git a/docs/metrics/resource metrics/organization.md b/docs/metrics/resource_metrics/organization.md
similarity index 100%
rename from docs/metrics/resource metrics/organization.md
rename to docs/metrics/resource_metrics/organization.md
diff --git a/docs/metrics/resource metrics/person.md b/docs/metrics/resource_metrics/person.md
similarity index 100%
rename from docs/metrics/resource metrics/person.md
rename to docs/metrics/resource_metrics/person.md
diff --git a/docs/metrics/resource metrics/publication.md b/docs/metrics/resource_metrics/publication.md
similarity index 100%
rename from docs/metrics/resource metrics/publication.md
rename to docs/metrics/resource_metrics/publication.md
diff --git a/docs/metrics/resource metrics/registry.md b/docs/metrics/resource_metrics/registry.md
similarity index 100%
rename from docs/metrics/resource metrics/registry.md
rename to docs/metrics/resource_metrics/registry.md
diff --git a/docs/metrics/resource metrics/repository.md b/docs/metrics/resource_metrics/repository.md
similarity index 100%
rename from docs/metrics/resource metrics/repository.md
rename to docs/metrics/resource_metrics/repository.md
diff --git a/docs/metrics/resource metrics/service.md b/docs/metrics/resource_metrics/service.md
similarity index 100%
rename from docs/metrics/resource metrics/service.md
rename to docs/metrics/resource_metrics/service.md
diff --git a/docs/metrics/resource metrics/software.md b/docs/metrics/resource_metrics/software.md
similarity index 100%
rename from docs/metrics/resource metrics/software.md
rename to docs/metrics/resource_metrics/software.md
diff --git a/docs/metrics/resource metrics/standards.md b/docs/metrics/resource_metrics/standards.md
similarity index 100%
rename from docs/metrics/resource metrics/standards.md
rename to docs/metrics/resource_metrics/standards.md
-- 
GitLab


From b6609d259ac4c19ab884f9eba515ba5831711793 Mon Sep 17 00:00:00 2001
From: Ralf Klammer <ralf.klammer@tu-dresden.de>
Date: Thu, 20 Mar 2025 15:34:06 +0100
Subject: [PATCH 50/59] [metrics] Add repo-url to config

---
 mkdocs.yml | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/mkdocs.yml b/mkdocs.yml
index 7b9d85b..65887ba 100644
--- a/mkdocs.yml
+++ b/mkdocs.yml
@@ -1,5 +1,7 @@
 site_name: NFDI4Earth - KnowledgeGraph - Questions & Metrics
 
+repo_url: https://git.rwth-aachen.de/nfdi4earth/knowledgehub/kh_questions
+
 theme:
   name: material
   language: de
-- 
GitLab


From c4af45cd15a69f23a714768a2129ac59819e422d Mon Sep 17 00:00:00 2001
From: Ralf Klammer <ralf.klammer@tu-dresden.de>
Date: Thu, 20 Mar 2025 15:34:31 +0100
Subject: [PATCH 51/59] [metrics] Modify index.md & add examples.md

---
 docs/examples.md | 124 +++++++++++++++++++++++++++++++++++++++++++++++
 docs/index.md    |  54 ++++++++++++++++++---
 2 files changed, 171 insertions(+), 7 deletions(-)
 create mode 100644 docs/examples.md

diff --git a/docs/examples.md b/docs/examples.md
new file mode 100644
index 0000000..7f952e7
--- /dev/null
+++ b/docs/examples.md
@@ -0,0 +1,124 @@
+# Examples
+
+This collection contains representative example queries that serve as entry points for using the NFDI4Earth KnowledgeGraph.
+
+## About this Collection
+
+The SPARQL queries presented here demonstrate fundamental query patterns and typical use cases. They are intentionally kept simple and serve as:
+
+- Examples for commonly needed query types
+- Templates for your own, more complex queries
+- Learning material for SPARQL beginners
+
+## Educational Resources Example
+
+This example demonstrates how to retrieve comprehensive information about educational resources from the NFDI4Earth Knowledge Hub.
+
+### Query Purpose
+
+The query finds all learning resources and their associated metadata, including:
+
+- Publishers and their names
+- Subject areas with labels
+- Licensing information
+- Related topics and their titles
+
+### SPARQL Features Demonstrated
+
+- Use of `CONSTRUCT` for graph pattern matching
+- Multiple `OPTIONAL` patterns for flexible data retrieval
+- Property path navigation
+- Handling of multiple vocabularies (schema.org, FOAF, DCT)
+
+### Query
+
+```sparql
+{{ include_if_exists("examples/Educational resources.rq") }}
+```
+
+### Understanding the Results
+
+The query returns a graph structure where:
+
+- Each learning resource is connected to its direct properties
+- Additional metadata about publishers, subjects, and licenses is included
+- Labels and titles are retrieved for better human readability
+
+
+## Repository Metadata Standards Example
+
+This example shows how to query metadata standards and API types supported by repositories in the NFDI4Earth Knowledge Hub.
+
+### Query Purpose
+
+The query retrieves information about:
+- Repository names
+- Supported metadata standards
+- Available API types and interfaces
+
+### SPARQL Features Demonstrated
+
+- Basic `SELECT` query structure
+- Multiple triple patterns
+- Property traversal
+- Use of domain-specific vocabularies (n4e)
+
+### Query
+
+```sparql
+{{ include_if_exists("examples/Metadata.rq") }}
+```
+
+### Understanding the Results
+
+The query returns:
+
+- Repository names for clear identification
+- Names of supported metadata standards
+- Types of APIs available for each repository
+
+This information is particularly useful for:
+
+- Understanding repository capabilities
+- Planning data integration
+- Evaluating technical compatibility
+
+## NFDI4Earth Services Example
+
+This example demonstrates how to query services that are provided by organizations within the NFDI4Earth consortium.
+
+### Query Purpose
+
+The query identifies:
+- Services (Repositories and Aggregators)
+- Their names and types
+- Publishing organizations within NFDI4Earth
+- Including services from sub-organizations
+
+### SPARQL Features Demonstrated
+
+- Complex `SELECT` query with `GROUP_CONCAT`
+- `UNION` patterns for alternative paths
+- Organization hierarchy traversal
+- Filter conditions with `NOT EXISTS`
+- Value constraints using `VALUES`
+
+### Query
+
+```sparql
+{{ include_if_exists("examples/Services in the NFDI4Earth.rq") }}
+```
+
+### Understanding the Results
+
+The query returns:
+
+- Service names and their types (Repository or Aggregator)
+- Concatenated list of publishing organizations
+- Services from both direct NFDI4Earth members and their sub-organizations
+
+This query is useful for:
+
+- Getting an overview of NFDI4Earth service landscape
+- Understanding organizational relationships
+- Service discovery and analysis
\ No newline at end of file
diff --git a/docs/index.md b/docs/index.md
index 31f12d7..a6b2ad4 100644
--- a/docs/index.md
+++ b/docs/index.md
@@ -2,20 +2,60 @@
 
 Welcome to the NFDI4Earth KnowledgeGraph Query Collection. This documentation provides insights into the NFDI4Earth KnowledgeHub Graph through SPARQL queries and their analysis.
 
+## About the Project
+
+The NFDI4Earth KnowledgeGraph represents a comprehensive network of earth science research data, connecting various domains, datasets, and research artifacts. Based on the NFDI4Earth metadata schema, it enables:
+
+- **Linked Research Data**: Integration of various data sources and research artifacts
+- **Semantic Search**: Intelligent discovery of relevant resources
+- **Community Integration**: Connecting researchers and their work
+
 ## Purpose
 
-The NFDI4Earth KnowledgeGraph represents a comprehensive network of earth science research data, connecting various domains, datasets, and research artifacts. Through SPARQL queries, we explore:
+Through SPARQL queries, we explore:
 
 - **Data Discovery**: Finding relevant research data across earth science domains
 - **Domain Coverage**: Understanding the breadth and depth of represented research areas
 - **Graph Structure**: Analyzing the knowledge graph's characteristics and connectivity
 
-## Exploration Areas
+## Query Collection
+
+Our queries are organized into three main categories.
+
+[**1. Basic Examples**](/examples)
+
+   - Common query patterns
+   - Graph exploration basics
+   - Getting started guides
+
+[**2. Domain Questions**](/questions)
+
+   - Domain-specific research questions
+   - Real-world use cases
+   - Complex query patterns
+
+[**3. Graph Metrics**](/metrics)
+
+   - Structural metrics
+   - Quality analysis
+   - Network characteristics
+
+## Getting Started
+
+Each query is documented with:
+- Detailed description of the use case
+- SPARQL code with explanations
+- Example results and visualizations
+- Interpretation guidelines
+- Performance considerations
+
+## Contributing
 
-We collect queries for three main purposes:
+We invite the community to contribute their own queries!
 
-1. **Basic Examples** to demonstrate common query patterns and graph exploration
-2. **Domain Questions** addressing specific research data discovery needs
-3. **Graph Metrics** providing insights into the knowledge graph's structure
+## Resources
 
-Each query is documented with its purpose, expected results, and practical implications for understanding and utilizing the NFDI4Earth KnowledgeHub.
+- [NFDI4Earth OneStop4All](https://onestop4all.nfdi4earth.de)
+- [KnowledgeHub Documentation](https://knowledgehub.nfdi4earth.de)
+- [SPARQL Endpoint](https://sparql.knowledgehub.nfdi4earth.de)
+- [GitHub Repository](https://git.rwth-aachen.de/nfdi4earth/knowledgehub/kh_questions)
-- 
GitLab


From 1a4181c4db2eabff917d5b65a761eb0bb563b0d4 Mon Sep 17 00:00:00 2001
From: Ralf Klammer <ralf.klammer@tu-dresden.de>
Date: Thu, 20 Mar 2025 15:49:52 +0100
Subject: [PATCH 52/59] [metrics] fix: links in main index.md

---
 docs/index.md | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/docs/index.md b/docs/index.md
index a6b2ad4..de2ff1d 100644
--- a/docs/index.md
+++ b/docs/index.md
@@ -22,19 +22,19 @@ Through SPARQL queries, we explore:
 
 Our queries are organized into three main categories.
 
-[**1. Basic Examples**](/examples)
+[**1. Basic Examples**](examples)
 
    - Common query patterns
    - Graph exploration basics
    - Getting started guides
 
-[**2. Domain Questions**](/questions)
+[**2. Domain Questions**](questions)
 
    - Domain-specific research questions
    - Real-world use cases
    - Complex query patterns
 
-[**3. Graph Metrics**](/metrics)
+[**3. Graph Metrics**](metrics)
 
    - Structural metrics
    - Quality analysis
-- 
GitLab


From 4bb1d5025cfbe0f86e1d21974fdcebe0d391d30a Mon Sep 17 00:00:00 2001
From: Ralf Klammer <ralf.klammer@tu-dresden.de>
Date: Thu, 20 Mar 2025 15:52:05 +0100
Subject: [PATCH 53/59] [metrics] fix: links in main index.md

---
 docs/index.md | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/docs/index.md b/docs/index.md
index de2ff1d..bedb30f 100644
--- a/docs/index.md
+++ b/docs/index.md
@@ -22,19 +22,19 @@ Through SPARQL queries, we explore:
 
 Our queries are organized into three main categories.
 
-[**1. Basic Examples**](examples)
+[**1. Basic Examples**](./examples)
 
    - Common query patterns
    - Graph exploration basics
    - Getting started guides
 
-[**2. Domain Questions**](questions)
+[**2. Domain Questions**](./questions)
 
    - Domain-specific research questions
    - Real-world use cases
    - Complex query patterns
 
-[**3. Graph Metrics**](metrics)
+[**3. Graph Metrics**](./metrics)
 
    - Structural metrics
    - Quality analysis
-- 
GitLab


From 33110c2c93dee514e5aff8ace15487182999777d Mon Sep 17 00:00:00 2001
From: Ralf Klammer <ralf.klammer@tu-dresden.de>
Date: Fri, 21 Mar 2025 09:36:00 +0100
Subject: [PATCH 54/59] [metrics] Add explanation on incoming & outgoing edges

---
 .../general_metrics/04_edges_incoming.md      | 35 ++++++++++++++++++
 .../general_metrics/05_edges_outgoing.md      | 37 +++++++++++++++++++
 2 files changed, 72 insertions(+)

diff --git a/docs/metrics/general_metrics/04_edges_incoming.md b/docs/metrics/general_metrics/04_edges_incoming.md
index d649181..c4ad423 100644
--- a/docs/metrics/general_metrics/04_edges_incoming.md
+++ b/docs/metrics/general_metrics/04_edges_incoming.md
@@ -2,6 +2,41 @@
 
 This metric determines the median number of incoming edges across all nodes in the graph. The calculation requires multiple steps.
 
+## Understanding Incoming Edges
+
+In a knowledge graph, incoming edges represent relationships where other nodes point to or reference a particular node. Think of them as "arrows" pointing towards a node. For example:
+
+- If Dataset A `references` Resource B, then Resource B has an incoming edge
+- If Organization X `publishes` Dataset Y, then Dataset Y has an incoming edge
+- If Service M `supports` Standard N, then Standard N has an incoming edge
+
+### Significance
+
+The number of incoming edges can indicate:
+- How frequently a resource is referenced or used
+- The centrality or importance of a node in the network
+- Potential bottlenecks or key connection points
+- The interconnectedness of different resources
+
+### Example
+
+```
+Resource A ---hasLicense---> License X  (X has 1 incoming edge)
+Resource B ---hasLicense---> License X  (X now has 2 incoming edges)
+Resource C ---hasLicense---> License X  (X now has 3 incoming edges)
+```
+
+```turtle
+@prefix ex: <http://example.org/> .
+@prefix dct: <http://purl.org/dc/terms/> .
+
+ex:ResourceA dct:license ex:LicenseX .
+ex:ResourceB dct:license ex:LicenseX .
+ex:ResourceC dct:license ex:LicenseX .
+```
+
+In this example, License X has 3 incoming edges, indicating it's a commonly used license in the graph.
+
 {{ metrics_table_single_general('edges_in') }}
 
 ## Queries
diff --git a/docs/metrics/general_metrics/05_edges_outgoing.md b/docs/metrics/general_metrics/05_edges_outgoing.md
index 5531d38..4b96894 100644
--- a/docs/metrics/general_metrics/05_edges_outgoing.md
+++ b/docs/metrics/general_metrics/05_edges_outgoing.md
@@ -2,6 +2,43 @@
 
 This metric determines the median number of outgoing edges across all nodes in the graph. The calculation requires multiple steps.
 
+## Understanding Outgoing Edges
+
+In a knowledge graph, outgoing edges represent relationships where a node points to or references other nodes. Think of them as "arrows" pointing away from a node. For example:
+
+- If Dataset A `references` Resource B, then Dataset A has an outgoing edge
+- If Organization X `publishes` Dataset Y, then Organization X has an outgoing edge
+- If Service M `supports` Standard N, then Service M has an outgoing edge
+
+### Significance
+
+The number of outgoing edges can indicate:
+- How many relationships a node initiates
+- The completeness of resource descriptions
+- Connection patterns and data modeling practices
+- The level of detail in resource metadata
+
+### Example
+
+```
+Dataset A ---dct:license-----> License X (A has now 1 outgoing edge)
+Dataset A ---dct:publisher---> Organization Y (A has now 2 outgoing edges)
+Dataset A ---schema:about----> Topic Z (A has now 3 outgoing edges)
+```
+
+```turtle
+@prefix ex: <http://example.org/> .
+@prefix dct: <http://purl.org/dc/terms/> .
+@prefix schema: <http://schema.org/> .
+
+# Dataset has 3 outgoing edges
+ex:DatasetA dct:license   ex:LicenseX ;
+            dct:publisher ex:OrganizationY ;
+            schema:about  ex:TopicZ .
+```
+
+In this example, Dataset A has 3 outgoing edges, demonstrating a well-described resource with license, publisher, and topic information.
+
 {{ metrics_table_single_general('edges_out') }}
 
 ## Queries
-- 
GitLab


From 9c0d112b9b4928442fce98cb8daa39549715322a Mon Sep 17 00:00:00 2001
From: Ralf Klammer <ralf.klammer@tu-dresden.de>
Date: Fri, 21 Mar 2025 09:56:55 +0100
Subject: [PATCH 55/59] [metrics] Update readme's

---
 README.md         | 60 ++++++++++++++++++++++++++++++-----------------
 scripts/README.MD | 44 ----------------------------------
 scripts/README.md | 60 +++++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 99 insertions(+), 65 deletions(-)
 delete mode 100644 scripts/README.MD
 create mode 100644 scripts/README.md

diff --git a/README.md b/README.md
index d750a1c..094b43d 100644
--- a/README.md
+++ b/README.md
@@ -5,48 +5,66 @@ A collection of SPARQL queries for analyzing the KnowledgeGraph of NFDI4Earth's
 ## Repository Structure
 
 ```
-queries/
-├── examples/       # Basic SPARQL examples
-├── questions/      # Domain-specific queries
-└── metrics/        # Graph analysis queries
-
-docs/
-├── examples.md     # Documentation for basic examples
-├── questions.md    # Documentation for domain questions
-└── metrics.md      # Documentation for graph metrics
+├── docs/
+│   ├── index.md                    # Main documentation page
+│   ├── examples.md
+│   ├── questions.md
+│   ├── macros/
+│   ├── metrics/
+│   │   ├── general_metrics/
+│   │   ├── resource_metrics/
+│   │   └── schema_complexity_metrics/
+│   └── queries/                   # Query documentation
+│       ├── examples/
+│       ├── metrics/
+│       └── questions/
+│
+└── scripts/                       # Python analysis tools
+    └── kg_analysis/
+        └── ...
 ```
 
 ## Usage
 
-All queries are stored in `.rq` files and can be executed against the NFDI4Earth KnowledgeGraph endpoint.
+The repository consists of three main components:
+
+1. **Documentation** (`/docs`): Comprehensive documentation of queries, examples and metrics
+2. **Query Collection** (`/docs/queries`): SPARQL queries organized by purpose
+3. **Analysis Tool** (`/scripts`): Python package for executing queries and calculating metrics
 
 ### Local Development
 
-0. Setup virtual environment
+1. Setup virtual environment:
 ```bash
 python3 -m venv venv
-. venv/bin/activate
+source venv/bin/activate
 ```
 
-1. Install dependencies:
+2. Install dependencies:
 ```bash
 pip install -r requirements.txt
+pip install -e scripts/
 ```
 
-2. Start local documentation server:
+3. Start documentation server:
 ```bash
 mkdocs serve
 ```
 
-3. Build documentation:
-```bash
-mkdocs build
-```
-
 ## Contributing
 
-We welcome contributions! Please check our contribution guidelines for adding new queries.
+We welcome contributions:
+
+- New SPARQL queries
+- Documentation improvements
+- Tool enhancements
+
+### Contributors
+
+Ralf Klammer, Auriol Degbelo, Jonas Grieb
 
 ## Contact
 
-For questions about the NFDI4Earth KnowledgeHub Graph, contact [helpdesk@nfdi4earth.de](mailto:helpdesk@nfdi4earth.de?subject=[NFDI4Earth][KnowlegeGraph]).
+For questions about the NFDI4Earth KnowledgeHub Graph:
+- Email: [helpdesk@nfdi4earth.de](mailto:helpdesk@nfdi4earth.de?subject=[NFDI4Earth][KnowlegeGraph])
+- Website: [https://knowledgehub.nfdi4earth.de/](https://knowledgehub.nfdi4earth.de)
\ No newline at end of file
diff --git a/scripts/README.MD b/scripts/README.MD
deleted file mode 100644
index bc7066b..0000000
--- a/scripts/README.MD
+++ /dev/null
@@ -1,44 +0,0 @@
-# KG Analysis Tool
-
-A command-line tool for analyzing SPARQL endpoints, specifically designed for the NFDI4Earth Knowledge Graph.
-
-## Features
-
-- Run SPARQL queries from files
-- Save query results to output files
-- Configurable SPARQL endpoint via environment variable
-
-## Installation
-
-```bash
-# Create and activate virtual environment
-python3 -m venv venv
-source venv/bin/activate
-
-# Install in development mode
-pip install -e .
-```
-
-## Usage
-
-Set the SPARQL endpoint (optional):
-```bash
-export KG_SPARQL_ENDPOINT="https://your-sparql-endpoint/sparql"
-```
-
-Run a query:
-```bash
-kg_analysis run-query -q path/to/query.rq -o path/to/output.txt
-```
-
-## Requirements
-
-- Python >= 3.10
-- click
-- SPARQLWrapper
-
-## License
-
-This project is published under the Apache License 2.0, see file `LICENSE`.
-
-Contributors: Ralf Klammer
diff --git a/scripts/README.md b/scripts/README.md
new file mode 100644
index 0000000..650e60a
--- /dev/null
+++ b/scripts/README.md
@@ -0,0 +1,60 @@
+# KG Analysis Tool
+
+A command-line tool for analyzing SPARQL endpoints, specifically designed for the NFDI4Earth Knowledge Graph and its metrics collection.
+
+## Features
+
+- Run SPARQL queries from files
+- Execute predefined metric queries
+- Save query results in JSON
+- Configurable SPARQL endpoint, request timeout & directory of reports via environment variables
+- Supports query parameters and templates
+
+## Installation
+
+```bash
+# Create and activate virtual environment
+python3 -m venv venv
+source venv/bin/activate
+
+# Install in development mode
+pip install -e .
+```
+
+## Usage
+
+### Environment Variables
+
+```bash
+# Required
+export SPARQL_ENDPOINT="https://sparql.knowledgehub.nfdi4earth.de"
+
+# Optional
+export SPARQL_TIMEOUT=120  # in seconds
+export REPORTS_DIR="./reports/metrics"
+```
+
+### CLI Commands
+
+Run a single query:
+
+```bash
+kg-analysis query -q path/to/query.rq
+```
+
+Request/Calculate all metrics:
+
+```bash
+kg-analysis metrics
+```
+
+## License
+
+This project is published under the Apache License 2.0, see file [`LICENSE`](LICENSE).
+
+## Related Projects
+
+- [NFDI4Earth KnowledgeHub](https://knowledgehub.nfdi4earth.de)
+- [OneStop4All](https://onestop4all.nfdi4earth.de)
+
+Contributors: Ralf Klammer
-- 
GitLab


From 18ade219627264e81f75ac6632254aaa2cc577a0 Mon Sep 17 00:00:00 2001
From: Ralf Klammer <ralf.klammer@tu-dresden.de>
Date: Fri, 21 Mar 2025 10:01:42 +0100
Subject: [PATCH 56/59] [metrics] Remove previous readme

---
 README.bkp.md | 173 --------------------------------------------------
 1 file changed, 173 deletions(-)
 delete mode 100644 README.bkp.md

diff --git a/README.bkp.md b/README.bkp.md
deleted file mode 100644
index 6f8681c..0000000
--- a/README.bkp.md
+++ /dev/null
@@ -1,173 +0,0 @@
-# KnowledgeHub - Domain Coverage
-
-This is collection of relevant questions and corresponding SPARQL-Queries, that answer those questions. The questions are grouped according to the different entities of interest (datasets, organizations, ...). The entities appear in alphabetical order. The first query is useful to get an overview of all entities available in the Knowledge Hub. The questions listed below form, altogether, the domain coverage of the Knowledge Hub. For details, see the [NFDI4Earth Deliverable D4.3.2](https://zenodo.org/records/7950860).
-
-***Overview of the types of entities***
-| ID    | Question | Query/ies |
-|---|---|---|
-| TY001	| What are the types of entities available in the knowledge graph? | [TY001](queries/TY001.rq)|
-
-&nbsp;
-&nbsp;
-
-### Aggregator
-
-| ID    | Question | Query/ies |
-|---|---|---|
-| AG001	| What are all entities of type Aggregator? | [AG001](queries/AG001.rq)|
-| AG001_2	| What are name and geometry of Aggregator? | [AG001_2](queries/AG001_2.rq)|
-| AG002_1	| What are all attributes available for the type "Aggregator"? | [AG002_1](queries/AG002_1.rq)|
-| AG002_2	| How many attributes are available for the type "Aggregator"? | [AG002_2](queries/AG002_2.rq)|
-
-### Article
-
-| ID    | Question | Query/ies |
-|---|---|---|
-| AT001	| What are all entities of type schema:Article? | [AT001](queries/AT001.rq)|
-| AT002_1	| What are all attributes available for the type "schema:Article"? | [AT002_1](queries/AT002_1.rq)|
-| AT002_2	| How many attributes are available for the type "schema:Article"? | [AT002_2](queries/AT002_2.rq)|
-
-### Dataset
-
-| ID    | Question | Query/ies |
-|---|---|---|
-| DA001	| What are all entities of type dcat:Dataset? | [DA001](queries/DA001.rq)|
-| DA002_1	| What are all attributes available for the type "dcat:Dataset"? | [DA002_1](queries/DA002_1.rq)|
-| DA002_2	| How many attributes are available for the type "dcat:Dataset"? | [DA002_2](queries/DA002_2.rq)|
-| DA003_1	| What are the datasets having the string 'world settlement footprint' in title or description? | [DA003_1](queries/DA003_1.rq)|
-
-### LHBArticle
-
-| ID    | Question | Query/ies |
-|---|---|---|
-| LH001	| What are all entities of type LHBArticle? | [LH001](queries/LH001.rq)|
-| LH002_1	| What are all attributes available for the type "LHBArticle"? | [LH002_1](queries/LH002_1.rq)|
-| LH002_2	| How many attributes are available for the type "LHBArticle"? | [LH002_2](queries/LH002_2.rq)|
-
-### LearningResource
-
-| ID    | Question | Query/ies |
-|---|---|---|
-| LR001	| What are all entities of type LearningResource? | [LR001](queries/LR001.rq)|
-| LR002_1	| What are all attributes available for the type "LearningResource"? | [LR002_1](queries/LR002_1.rq)|
-| LR002_2	| How many attributes are available for the type "LearningResource"? | [LR002_2](queries/LR002_2.rq)|
-
-### MetadataStandard
-
-| ID    | Question | Query/ies |
-|---|---|---|
-| MS001	| What are all entities of type MetadataStandard? | [MS001](queries/MS001.rq)|
-| MS002_1	| What are all attributes available for the type "MetadataStandard"? | [MS002_1](queries/MS002_1.rq)|
-| MS002_2	| How many attributes are available for the type "MetadataStandard"? | [MS002_2](queries/MS002_2.rq)|
-
-### Organization
-
-| ID    | Question | Query/ies |
-|---|---|---|
-| OG001	| What are all entities of type Organization? | [OG001](queries/OG001.rq)|
-| OG002_1	| What are all attributes available for the type "Organization"? | [OG002_1](queries/OG002_1.rq)|
-| OG002_2	| How many attributes are available for the type "Organization"? | [OG002_2](queries/OG002_2.rq)|
-
-### Person
-
-| ID    | Question | Query/ies |
-|---|---|---|
-| PE001	| What are all entities of type Person? | [PE001](queries/PE001.rq)|
-| PE002_1	| What are all attributes available for the type "Person"? | [PE002_1](queries/PE002_1.rq)|
-| PE002_2	| How many attributes are available for the type "Person"? | [PE002_2](queries/PE002_2.rq)|
-
-### Registry
-
-| ID    | Question | Query/ies |
-|---|---|---|
-| REG001	| What are all entities of type Registry? | [REG001](queries/REG001.rq)|
-| REG002_1	| What are all attributes available for the type "Registry"? | [REG002_1](queries/REG002_1.rq)|
-| REG002_2	| How many attributes are available for the type "Registry"? | [REG002_2](queries/REG002_2.rq)|
-
-### Repository
-
-| ID    | Question | Query/ies |
-|---|---|---|
-| REP001	| What are all entities of type Repository? | [REP001](queries/REP001.rq)|
-| REP002_1	| What are all attributes available for the type "Repository"? | [REP002_1](queries/REP002_1.rq)|
-| REP002_2	| How many attributes are available for the type "Repository"? | [REP002_2](queries/REP002_2.rq)|
-
-### ResearchProject
-
-| ID    | Question | Query/ies |
-|---|---|---|
-| RP001	| What are all entities of type ResearchProject? | [RP001](queries/RP001.rq)|
-| RP002_1	| What are all attributes available for the type "ResearchProject"? | [RP002_1](queries/RP002_1.rq)|
-| RP002_2	| How many attributes are available for the type "ResearchProject"? | [RP002_2](queries/RP002_2.rq)|
-
-
-### SoftwareSourceCode
-
-| ID    | Question | Query/ies |
-|---|---|---|
-| SC001	| What are all entities of type SoftwareSourceCode? | [SC001](queries/SC001.rq)|
-| SC002_1	| What are all attributes available for the type "SoftwareSourceCode"? | [SC002_1](queries/SC002_1.rq)|
-| SC002_2	| How many attributes are available for the type "SoftwareSourceCode"? | [SC002_2](queries/SC002_2.rq)|
-
-### Graph Metrics
-
-| ID    | Question | Query/ies |
-|---|---|---|
-|GM001|The number of instances in a graph|[GM001](queries/GM001.rq)|
-|GM002|The number of assertions (or edges between entities)|[GM002](queries/GM002.rq)|
-||||
-||||
-||||
-||||
-
-
-<!--- Template for a new table (including first line)
-
-### EntityType
-
-| ID    | Question | Query/ies |
-|---|---|---|
-| XX001	| What are all entities of type EntityType? | [XX001](queries/XX001.rq)|
-
--->
-
-
-<!---
-
-
-### Organizations
-
-| ID    | Question | Query/ies |
-|---|---|---|
-| OR001	| What is the URL of the homepage for the organization with the following name: 'Karlsruhe Institute of Technology'? | [OR001_1](queries/OR001_1.rq),[OR001_2](queries/OR001_2.rq) |
-| OR002 | What is the URL of the homepage for the organization with the following ID: 'https://nfdi4earth-knowledgehub.geo.tu-dresden.de/api/objects/n4ekh/a38143be5e15bed94a20' | [OR003_1](queries/OR003_1.rq) |
-| OR003 | Which organizations have not defined any homepage? | [OR003_1](queries/OR003_1.rq) |
-| OR004 | Which services are published by the organization? | [OR004_1](queries/OR004_1.rq) |
-| OR005 | What is the geolocation of the organization called 'TU Dresden'? | [OR005_1](queries/OR005_1.rq) |
-| OR006 | What is the geolocation of all organizations, that are members of the NFDI4Earth consortium? | [OR006_1](queries/OR006_1.rq) |
-
-### Repositories
-
-| ID | Question | Query/ies |
-|----|----------|-----------|
-| DR1 | At which repository can I archive my [geophysical] data of [2] GB?| [OR004_1](queries/OR004_1.rq) |
-| DR2 | What is the temporal coverage of a data repository?||
-| DR3 | What is the spatial coverage of a data repository?||
-| DR4 | What is the curation policy of the data repository?||
-| DR5 | Which licences are supported by the data repository?||
-| DR6 | Does the repository give identifiers for its ressources?||
-| DR7 | Which metadata harversting interface is supported by the repository?||
-| DR8 | Which type of (persistent) identifiers are used by the repository?||
-| DR9 | What is the thematic area/subject of a repository?||
-| DR10 | Limitations of data deposit at the repository?||
-| DR11 | When was the medatada for a given repository first collected/last updated?||
-| DR12 | Is the repository still available?||
-| DR13 | Which repository allows long term archiving?||
-
--->
-
-# Notes
-
-This question-based approach takes inspiration from the [GeoSPARQLBenchmark](https://github.com/OpenLinkSoftware/GeoSPARQLBenchmark).
-
-It is directly linked to the [Knowledge Hub landing page project](https://git.rwth-aachen.de/nfdi4earth/knowledgehub/kh_landingpage) as all the questions and examples are taken to explain the basic idea and demonstrate usage of the [Knowledge Hub](https://knowledgehub.nfdi4earth.de).
-- 
GitLab


From e139a035029a04258bb4f7105a0c3b17118ca898 Mon Sep 17 00:00:00 2001
From: Ralf Klammer <ralf.klammer@tu-dresden.de>
Date: Fri, 21 Mar 2025 10:16:41 +0100
Subject: [PATCH 57/59] [metrics] config site in english

---
 mkdocs.yml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/mkdocs.yml b/mkdocs.yml
index 65887ba..8f1baff 100644
--- a/mkdocs.yml
+++ b/mkdocs.yml
@@ -4,7 +4,7 @@ repo_url: https://git.rwth-aachen.de/nfdi4earth/knowledgehub/kh_questions
 
 theme:
   name: material
-  language: de
+  language: en
   features:
     - search.highlight
     - content.code.copy
-- 
GitLab


From 38c050193af5be81350894b1a5866b553dd5d28d Mon Sep 17 00:00:00 2001
From: Ralf Klammer <ralf.klammer@tu-dresden.de>
Date: Fri, 21 Mar 2025 10:34:18 +0100
Subject: [PATCH 58/59] [metrics] modify navbar - colors, symbol & favicon

---
 docs/assets/NFDI4Earth_Symbol.png | Bin 0 -> 92560 bytes
 docs/assets/favicon.ico           | Bin 0 -> 32988 bytes
 mkdocs.yml                        |  14 +++++---------
 3 files changed, 5 insertions(+), 9 deletions(-)
 create mode 100644 docs/assets/NFDI4Earth_Symbol.png
 create mode 100644 docs/assets/favicon.ico

diff --git a/docs/assets/NFDI4Earth_Symbol.png b/docs/assets/NFDI4Earth_Symbol.png
new file mode 100644
index 0000000000000000000000000000000000000000..1a357281f6c49602f4c52ce75850e81db513ae8f
GIT binary patch
literal 92560
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%0Ar;N$k8#g(`
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
Pd@*>s`njxgN@xNA4)oyD

literal 0
HcmV?d00001

diff --git a/docs/assets/favicon.ico b/docs/assets/favicon.ico
new file mode 100644
index 0000000000000000000000000000000000000000..83fd5498b6832c052576e708cbd4e5e5eda77d6d
GIT binary patch
literal 32988
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

literal 0
HcmV?d00001

diff --git a/mkdocs.yml b/mkdocs.yml
index 8f1baff..329d821 100644
--- a/mkdocs.yml
+++ b/mkdocs.yml
@@ -5,9 +5,14 @@ repo_url: https://git.rwth-aachen.de/nfdi4earth/knowledgehub/kh_questions
 theme:
   name: material
   language: en
+  logo: assets/NFDI4Earth_Symbol.png
+  favicon: assets/favicon.ico
   features:
     - search.highlight
     - content.code.copy
+  palette:
+    - primary: blue
+      accent: cyan
 
 plugins:
   - search
@@ -21,12 +26,3 @@ exclude_docs:
 markdown_extensions:
   - admonition
   - pymdownx.details
-#   - pymdownx.superfences
-#   - pymdownx.snippets:
-#       base_path: ['.']
-#       check_paths: true
-#   - pymdownx.highlight:
-#       anchor_linenums: true
-#       line_spans: __span
-#       pygments_lang_class: true
-#   - pymdownx.inlinehilite
\ No newline at end of file
-- 
GitLab


From b1b4d6fc8ed4c1910be4d9cc9eac16dd53f07487 Mon Sep 17 00:00:00 2001
From: Ralf Klammer <ralf.klammer@tu-dresden.de>
Date: Wed, 30 Apr 2025 14:14:41 +0200
Subject: [PATCH 59/59] [metrics] refine references on complexity measurement

---
 docs/metrics/index.md | 26 +++++++++++---------------
 1 file changed, 11 insertions(+), 15 deletions(-)

diff --git a/docs/metrics/index.md b/docs/metrics/index.md
index 064667b..d58e1a5 100644
--- a/docs/metrics/index.md
+++ b/docs/metrics/index.md
@@ -79,25 +79,21 @@ This formula provides a single numerical value representing schema complexity, w
 
 ### References
 
-> !!!Not the final references!!!
+> Gómez-Pérez et al. (2004) – "Evaluation of Ontologies" - Describes metrics like number of classes, hierarchy depth, and relations
+>
+> *[https://link.springer.com/chapter/10.1007/978-3-540-30202-5_3](https://link.springer.com/chapter/10.1007/978-3-540-30202-5_3)*
 
-1. Structural Metrics for Ontologies:
+2) OWL and Schema Complexity Measurements:
 
-- Gómez-Pérez et al. (2004) – "Evaluation of Ontologies"
-- Describes metrics like number of classes, hierarchy depth, and relations
-- Source: https://link.springer.com/chapter/10.1007/978-3-540-30202-5_3
+> Tartir & Arpinar (2010) – "Ontology Evaluation and Ranking using OntoQA" - Develops the OntoQA model combining structural and semantic metrics
+>
+> *[https://ieeexplore.ieee.org/document/4338348](https://ieeexplore.ieee.org/document/4338348)*
 
-2. OWL and Schema Complexity Measurements:
+3) SPARQL Analysis and RDF Complexity:
 
-   - Tartir & Arpinar (2010) – "Ontology Evaluation and Ranking using OntoQA"
-   - Develops the OntoQA model combining structural and semantic metrics
-   - Source: https://doi.org/10.1109/ICDEW.2005.43
-
-3. SPARQL Analysis and RDF Complexity:
-
-   - Lanzenberger et al. (2008) – "Ontology Evaluation – State of the Art"
-   - Describes hierarchical depth as key metric for RDF schema complexity
-   - Source: https://doi.org/10.1007/978-3-540-92673-3_10
+> Lanzenberger et al. (2008) – "Ontology Evaluation – State of the Art" - Describes hierarchical depth as key metric for RDF schema complexity
+>
+> *[https://doi.org/10.1007/978-3-540-92673-3_10](https://doi.org/10.1007/978-3-540-92673-3_10)*
 
 ## About the Metrics
 
-- 
GitLab