c2score 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- c2score-0.1.0/.github/workflows/ci.yml +59 -0
- c2score-0.1.0/.gitignore +9 -0
- c2score-0.1.0/CHANGELOG.md +30 -0
- c2score-0.1.0/LICENSE +21 -0
- c2score-0.1.0/PKG-INFO +314 -0
- c2score-0.1.0/README.md +287 -0
- c2score-0.1.0/pyproject.toml +48 -0
- c2score-0.1.0/src/c2score/__init__.py +25 -0
- c2score-0.1.0/src/c2score/_instance_json.py +165 -0
- c2score-0.1.0/src/c2score/cardinality.py +94 -0
- c2score-0.1.0/src/c2score/conciseness.py +191 -0
- c2score-0.1.0/src/c2score/coverage.py +222 -0
- c2score-0.1.0/src/c2score/datatypes.py +108 -0
- c2score-0.1.0/src/c2score/errors.py +17 -0
- c2score-0.1.0/src/c2score/evaluate.py +264 -0
- c2score-0.1.0/src/c2score/flatten.py +185 -0
- c2score-0.1.0/src/c2score/loaders/__init__.py +5 -0
- c2score-0.1.0/src/c2score/loaders/neo4j.py +270 -0
- c2score-0.1.0/src/c2score/model.py +379 -0
- c2score-0.1.0/src/c2score/resources/schema.json +74 -0
- c2score-0.1.0/src/c2score/schema_json.py +392 -0
- c2score-0.1.0/src/c2score/similarity.py +194 -0
- c2score-0.1.0/tests/data/example1.json +22 -0
- c2score-0.1.0/tests/data/example2.json +9 -0
- c2score-0.1.0/tests/data/example3.json +19 -0
- c2score-0.1.0/tests/data/fig1a.json +18 -0
- c2score-0.1.0/tests/data/mixed.json +32 -0
- c2score-0.1.0/tests/reference_impl.py +290 -0
- c2score-0.1.0/tests/test_abs_from_graph.py +136 -0
- c2score-0.1.0/tests/test_cardinality.py +106 -0
- c2score-0.1.0/tests/test_datatypes.py +118 -0
- c2score-0.1.0/tests/test_evaluate.py +476 -0
- c2score-0.1.0/tests/test_fast_matches_reference.py +217 -0
- c2score-0.1.0/tests/test_flatten.py +183 -0
- c2score-0.1.0/tests/test_flatten_self_loop.py +74 -0
- c2score-0.1.0/tests/test_instance_json.py +131 -0
- c2score-0.1.0/tests/test_model.py +150 -0
- c2score-0.1.0/tests/test_neo4j_integration.py +187 -0
- c2score-0.1.0/tests/test_neo4j_loader.py +316 -0
- c2score-0.1.0/tests/test_package.py +5 -0
- c2score-0.1.0/tests/test_schema_json.py +485 -0
- c2score-0.1.0/tests/test_similarity.py +213 -0
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
pull_request:
|
|
6
|
+
|
|
7
|
+
jobs:
|
|
8
|
+
test:
|
|
9
|
+
runs-on: ubuntu-24.04
|
|
10
|
+
strategy:
|
|
11
|
+
fail-fast: false
|
|
12
|
+
matrix:
|
|
13
|
+
python-version: ["3.10", "3.11", "3.12", "3.13"]
|
|
14
|
+
steps:
|
|
15
|
+
- uses: actions/checkout@v7
|
|
16
|
+
- uses: actions/setup-python@v7
|
|
17
|
+
with:
|
|
18
|
+
python-version: ${{ matrix.python-version }}
|
|
19
|
+
- name: Install
|
|
20
|
+
run: python -m pip install -e ".[dev]"
|
|
21
|
+
- name: Test
|
|
22
|
+
run: python -m pytest
|
|
23
|
+
|
|
24
|
+
neo4j:
|
|
25
|
+
# Integration test #3 against a real Neo4j server.
|
|
26
|
+
runs-on: ubuntu-24.04
|
|
27
|
+
services:
|
|
28
|
+
neo4j:
|
|
29
|
+
image: neo4j:5.26.0-community
|
|
30
|
+
env:
|
|
31
|
+
NEO4J_AUTH: neo4j/c2score-ci
|
|
32
|
+
ports:
|
|
33
|
+
- 7687:7687
|
|
34
|
+
options: >-
|
|
35
|
+
--health-cmd "cypher-shell -u neo4j -p c2score-ci 'RETURN 1'"
|
|
36
|
+
--health-interval 5s
|
|
37
|
+
--health-timeout 10s
|
|
38
|
+
--health-retries 30
|
|
39
|
+
env:
|
|
40
|
+
C2SCORE_TEST_NEO4J_URI: bolt://localhost:7687
|
|
41
|
+
C2SCORE_TEST_NEO4J_USER: neo4j
|
|
42
|
+
C2SCORE_TEST_NEO4J_PASSWORD: c2score-ci
|
|
43
|
+
# Community edition has a single user database.
|
|
44
|
+
C2SCORE_TEST_NEO4J_DATABASE: neo4j
|
|
45
|
+
steps:
|
|
46
|
+
- uses: actions/checkout@v7
|
|
47
|
+
- uses: actions/setup-python@v7
|
|
48
|
+
with:
|
|
49
|
+
python-version: "3.12"
|
|
50
|
+
- name: Install
|
|
51
|
+
run: python -m pip install -e ".[dev]"
|
|
52
|
+
- name: Test
|
|
53
|
+
# bash here means -eo pipefail, so a pytest failure is not hidden by tee.
|
|
54
|
+
# Also fail if the tests were skipped instead of run.
|
|
55
|
+
shell: bash
|
|
56
|
+
run: |
|
|
57
|
+
set -o pipefail
|
|
58
|
+
python -m pytest tests/test_neo4j_integration.py -v -rs | tee pytest.log
|
|
59
|
+
! grep -q SKIPPED pytest.log
|
c2score-0.1.0/.gitignore
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 0.1.0 (unreleased)
|
|
4
|
+
|
|
5
|
+
First release.
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
- `load_schema_json`: read a schema from a JSON file or a `dict` and validate it. Every violation
|
|
10
|
+
raises `SchemaError` with its location. The format is also shipped as a JSON Schema
|
|
11
|
+
(`c2score/resources/schema.json`).
|
|
12
|
+
- `load_neo4j`: summarize a Neo4j 5.13+ database into its node and edge types (labels,
|
|
13
|
+
properties with data types, and cardinalities). The aggregation runs on the server in a
|
|
14
|
+
read-only session; APOC is not needed.
|
|
15
|
+
- `Instance.to_json` and `Instance.from_json`: save and restore such a summary.
|
|
16
|
+
- `evaluate`: compute C2-score with Coverage and Conciseness for node and edge types, the
|
|
17
|
+
redundant definitions, and the best-matching schema type for every instance type.
|
|
18
|
+
Parameters: `label_weight`, `edge_weights`, `redundancy_ratio`, `use_types`, `allow_any`.
|
|
19
|
+
- Schema features: data types (`STRING`, `INTEGER`, `FLOAT`, `BOOLEAN`, `DATETIME`, `ANY`),
|
|
20
|
+
mandatory and optional properties, cardinalities (`0..1`, `0..*`, `1..1`, `1..*`), and multiple
|
|
21
|
+
inheritance of node and edge types, including inherited edge endpoints.
|
|
22
|
+
- Exceptions: `C2ScoreError` and its subclasses `SchemaError`, `InstanceLoadError`, and `ParameterError`.
|
|
23
|
+
- Scales to large graphs and schemas: edge similarities are computed only for schema edge types
|
|
24
|
+
that can match, and Conciseness reuses the computed similarities when removing a definition
|
|
25
|
+
does not change the rest of the flattened schema. A DBpedia graph with about 20 million nodes
|
|
26
|
+
and 45 million edges loads in about 6 minutes and is evaluated against a schema with about
|
|
27
|
+
180,000 edge definitions in about 8 minutes.
|
|
28
|
+
|
|
29
|
+
The library refines the definitions of the paper, so its scores can differ from those reported in
|
|
30
|
+
the paper. To reproduce the results of the paper, use https://github.com/kde-salt/C2-SCORE.
|
c2score-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Fusuke Yukawa and Hiroaki Shiokawa
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
c2score-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,314 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: c2score
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: C2-score: quantitative quality evaluation of property graph schemas without ground truth
|
|
5
|
+
Project-URL: Repository, https://github.com/kde-salt/c2score
|
|
6
|
+
Author: Fusuke Yukawa, Hiroaki Shiokawa
|
|
7
|
+
License-Expression: MIT
|
|
8
|
+
License-File: LICENSE
|
|
9
|
+
Keywords: graph database,neo4j,property graph,schema,schema quality
|
|
10
|
+
Classifier: Development Status :: 3 - Alpha
|
|
11
|
+
Classifier: Intended Audience :: Science/Research
|
|
12
|
+
Classifier: Programming Language :: Python :: 3
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
17
|
+
Classifier: Topic :: Database
|
|
18
|
+
Requires-Python: >=3.10
|
|
19
|
+
Requires-Dist: jsonschema
|
|
20
|
+
Requires-Dist: neo4j<6,>=5
|
|
21
|
+
Requires-Dist: numpy
|
|
22
|
+
Provides-Extra: dev
|
|
23
|
+
Requires-Dist: build; extra == 'dev'
|
|
24
|
+
Requires-Dist: pytest; extra == 'dev'
|
|
25
|
+
Requires-Dist: twine; extra == 'dev'
|
|
26
|
+
Description-Content-Type: text/markdown
|
|
27
|
+
|
|
28
|
+
# c2score
|
|
29
|
+
|
|
30
|
+
C2-score measures the quality of a property graph schema **without a ground-truth schema**.
|
|
31
|
+
Given a property graph instance and a candidate schema, it computes two scores in [0, 1]:
|
|
32
|
+
|
|
33
|
+
- **Coverage**: how well the schema explains the node and edge types observed in the instance
|
|
34
|
+
(labels, properties and their data types, endpoints, and cardinalities).
|
|
35
|
+
- **Conciseness**: how free the schema is of redundant definitions, that is, definitions that
|
|
36
|
+
can be removed without a meaningful loss of Coverage.
|
|
37
|
+
|
|
38
|
+
C2-score is the harmonic mean of the two. Each score is also reported separately for nodes and
|
|
39
|
+
edges, together with the redundant definitions and the best-matching schema type for every type
|
|
40
|
+
in the instance, so that you can see *why* a schema scores as it does.
|
|
41
|
+
|
|
42
|
+
The measure is described in the paper *C2-score: Quantitative Quality Evaluation of Property Graph
|
|
43
|
+
Schemas without Ground Truth* (EDBT 2027).
|
|
44
|
+
This library refines the definitions of the paper, so its scores can differ from those reported in
|
|
45
|
+
the paper. To reproduce the results of the paper, use the artifact repository
|
|
46
|
+
[kde-salt/C2-SCORE](https://github.com/kde-salt/C2-SCORE).
|
|
47
|
+
|
|
48
|
+
## Citation
|
|
49
|
+
|
|
50
|
+
If you use c2score in your research, please cite:
|
|
51
|
+
|
|
52
|
+
```bibtex
|
|
53
|
+
@inproceedings{yukawa2027c2score,
|
|
54
|
+
author = {Fusuke Yukawa and Hiroaki Shiokawa},
|
|
55
|
+
title = {{C2-score}: Quantitative Quality Evaluation of Property Graph Schemas without Ground Truth},
|
|
56
|
+
booktitle = {Proceedings of the International Conference on Extending Database Technology (EDBT)},
|
|
57
|
+
year = {2027}
|
|
58
|
+
}
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
(The bibliographic details will be completed when the proceedings are published.)
|
|
62
|
+
|
|
63
|
+
## Installation
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
pip install c2score
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
Python 3.10 or later is required. The dependencies (`numpy`, `jsonschema`, and the `neo4j` driver 5.x)
|
|
70
|
+
are installed automatically.
|
|
71
|
+
|
|
72
|
+
## Quick start
|
|
73
|
+
|
|
74
|
+
### 1. Create an instance summary
|
|
75
|
+
|
|
76
|
+
c2score evaluates a schema against a summary of the graph: its node and edge types with their
|
|
77
|
+
properties and cardinalities. Read the summary from Neo4j and save it as `instance.json`. The
|
|
78
|
+
aggregation runs on the server; the graph is not copied to Python.
|
|
79
|
+
|
|
80
|
+
```python
|
|
81
|
+
from c2score import load_neo4j
|
|
82
|
+
|
|
83
|
+
instance = load_neo4j("bolt://localhost:7687", auth=("neo4j", "password"), database="neo4j")
|
|
84
|
+
instance.to_json("instance.json")
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
Suppose the summary below is created:
|
|
88
|
+
|
|
89
|
+
```json
|
|
90
|
+
{
|
|
91
|
+
"format": "c2score-instance",
|
|
92
|
+
"version": 1,
|
|
93
|
+
"node_types": [
|
|
94
|
+
{"labels": ["Company"], "properties": {"name": {"type": "STRING", "required": true}}},
|
|
95
|
+
{"labels": ["Person"], "properties": {"name": {"type": "STRING", "required": true}}}
|
|
96
|
+
],
|
|
97
|
+
"edge_types": [
|
|
98
|
+
{"labels": ["WORKS_AT"], "source_labels": ["Person"], "target_labels": ["Company"],
|
|
99
|
+
"cardinality": {"out": "0..1", "in": "0..*"}, "properties": {}}
|
|
100
|
+
]
|
|
101
|
+
}
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
### 2. Write a schema
|
|
105
|
+
|
|
106
|
+
Write the schema to evaluate as `schema.json` (see "Writing a schema" below), for example:
|
|
107
|
+
|
|
108
|
+
```json
|
|
109
|
+
{
|
|
110
|
+
"node_types": [
|
|
111
|
+
{"id": "personType", "labels": ["Person"], "properties": {"name": {"type": "STRING"}}},
|
|
112
|
+
{"id": "companyType", "labels": ["Company"], "properties": {"name": {"type": "STRING"}}}
|
|
113
|
+
],
|
|
114
|
+
"edge_types": [
|
|
115
|
+
{"id": "worksAtType", "labels": ["WORKS_AT"], "source": "personType", "target": "companyType",
|
|
116
|
+
"cardinality": {"out": "0..1", "in": "0..*"}}
|
|
117
|
+
]
|
|
118
|
+
}
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
### 3. Evaluate
|
|
122
|
+
|
|
123
|
+
```python
|
|
124
|
+
from c2score import Instance, evaluate, load_schema_json
|
|
125
|
+
|
|
126
|
+
instance = Instance.from_json("instance.json")
|
|
127
|
+
schema = load_schema_json("schema.json")
|
|
128
|
+
result = evaluate(instance, schema)
|
|
129
|
+
|
|
130
|
+
print(result.c2) # C2-score
|
|
131
|
+
print(result.coverage, result.conciseness) # overall Coverage and Conciseness
|
|
132
|
+
print(result.cov_v, result.cov_e, result.con_v, result.con_e)
|
|
133
|
+
print(result.redundant_types) # {"node_types": [...], "edge_types": [...]}
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
A saved summary can be evaluated against any number of schemas without reading the graph again.
|
|
137
|
+
|
|
138
|
+
### The result
|
|
139
|
+
|
|
140
|
+
`evaluate` returns an `EvaluationResult`, a read-only dataclass:
|
|
141
|
+
|
|
142
|
+
| Attribute | Meaning |
|
|
143
|
+
| --- | --- |
|
|
144
|
+
| `c2` | C2-score: the harmonic mean of `coverage` and `conciseness` (0 if both are 0) |
|
|
145
|
+
| `coverage`, `conciseness` | `(cov_v + cov_e) / 2` and `(con_v + con_e) / 2` |
|
|
146
|
+
| `cov_v`, `cov_e`, `con_v`, `con_e` | Coverage and Conciseness of node types and of edge types |
|
|
147
|
+
| `redundant_types` | `{"node_types": [id, ...], "edge_types": [id, ...]}`: the redundant definitions, in id order |
|
|
148
|
+
| `best_matches` | `{"node_types": [...], "edge_types": [...]}`: one entry per type of the instance (see below) |
|
|
149
|
+
| `params` | The parameters actually used, including the edge weights and whether they were chosen automatically (`"edge_weights_mode": "auto"` or `"user"`) |
|
|
150
|
+
| `to_dict()` | Everything above as a JSON-serializable `dict` |
|
|
151
|
+
|
|
152
|
+
The dataclass is frozen, but the dictionaries and lists in `redundant_types`, `best_matches`, and
|
|
153
|
+
`params` are ordinary Python objects: modifying them modifies the result. Use `to_dict()` to get an
|
|
154
|
+
independent copy that you can change.
|
|
155
|
+
|
|
156
|
+
An entry of `best_matches["node_types"]` looks like
|
|
157
|
+
`{"labels": ["Person"], "schema_type": "personType", "similarity": 1.0}`.
|
|
158
|
+
An entry of `best_matches["edge_types"]` also has `source_labels` and `target_labels` (of the
|
|
159
|
+
instance type) and `source` and `target` (the ids of the schema node types at the two ends).
|
|
160
|
+
Labels are sorted. When the best similarity is 0, `schema_type` (and, for edges, `source` and
|
|
161
|
+
`target`) is `None`. When several schema types tie, the first one in a fixed order is reported
|
|
162
|
+
(node types by id; edge types by the smallest id of the definitions that produce them, then by
|
|
163
|
+
the source and target ids).
|
|
164
|
+
|
|
165
|
+
## Writing a schema
|
|
166
|
+
|
|
167
|
+
A schema is a JSON object with two arrays, `node_types` and `edge_types`. Both may be omitted.
|
|
168
|
+
`load_schema_json` accepts a path to a UTF-8 JSON file (a byte order mark is allowed) or an
|
|
169
|
+
already parsed `dict`.
|
|
170
|
+
|
|
171
|
+
| Key | In | Required | Meaning and default |
|
|
172
|
+
| --- | --- | --- | --- |
|
|
173
|
+
| `id` | node and edge types | yes | A non-empty string, unique across node **and** edge types |
|
|
174
|
+
| `labels` | node and edge types | no | An array of labels without duplicates. Default `[]`. A type inheriting from others lists only its own labels |
|
|
175
|
+
| `properties` | node and edge types | no | An object mapping a property name to `{"type": ..., "required": ...}`. Default `{}` |
|
|
176
|
+
| `properties.*.type` | | no | `STRING`, `INTEGER`, `FLOAT`, `BOOLEAN`, `DATETIME`, or `ANY` (case-insensitive). **Default `ANY`** |
|
|
177
|
+
| `properties.*.required` | | no | `true` (mandatory) or `false` (optional). **Default `true`** |
|
|
178
|
+
| `supertypes` | node and edge types | no | The ids of the direct parents, without duplicates. **The order matters** (see below). Node types inherit only from node types, edge types only from edge types |
|
|
179
|
+
| `source`, `target` | edge types | no | The ids of the node types at the two ends. If omitted, they are inherited from the nearest parent edge type that has them |
|
|
180
|
+
| `cardinality` | edge types | no | `{"out": interval, "in": interval}`, both required. An interval is one of `"0..1"`, `"0..*"`, `"1..1"`, `"1..*"`. If omitted, it is inherited; if no ancestor has one, it is treated as `0..*` in both directions |
|
|
181
|
+
|
|
182
|
+
The top level may also contain `"format": "c2score-schema"` and a `"version"` string; both are optional.
|
|
183
|
+
|
|
184
|
+
A minimal schema, in which `name` is mandatory and `email` optional, both of type `ANY`:
|
|
185
|
+
|
|
186
|
+
```json
|
|
187
|
+
{
|
|
188
|
+
"node_types": [
|
|
189
|
+
{"id": "personType", "labels": ["Person"],
|
|
190
|
+
"properties": {"name": {}, "email": {"required": false}}}
|
|
191
|
+
],
|
|
192
|
+
"edge_types": [
|
|
193
|
+
{"id": "knowsType", "labels": ["KNOWS"], "source": "personType", "target": "personType"}
|
|
194
|
+
]
|
|
195
|
+
}
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
### Inheritance
|
|
199
|
+
|
|
200
|
+
A type inherits the labels, properties, cardinality, and (for edge types) endpoints of its
|
|
201
|
+
ancestors.
|
|
202
|
+
|
|
203
|
+
- **Labels** are the union of the labels of the type and all its ancestors.
|
|
204
|
+
- **Properties, cardinality, and endpoints** are taken from the nearest type that defines them.
|
|
205
|
+
The type itself comes first, followed by its ancestors in breadth-first order, visiting the
|
|
206
|
+
`supertypes` of each type in the order written. The description of a property (type and
|
|
207
|
+
`required`) is taken as a whole from one type.
|
|
208
|
+
- The `source` and `target` of an edge type are inherited independently. Every edge type must end
|
|
209
|
+
up with both.
|
|
210
|
+
- **An edge type also applies to the descendants of its endpoints.** An edge type from `s` to `t`
|
|
211
|
+
stands for every pair (s′, t′) where s′ is `s` or one of its descendants and t′ is `t` or one of
|
|
212
|
+
its descendants.
|
|
213
|
+
- **Inheritance cycles are rejected**, and so is a type listing itself in `supertypes`.
|
|
214
|
+
|
|
215
|
+
Multiple inheritance and an inherited edge type:
|
|
216
|
+
|
|
217
|
+
```json
|
|
218
|
+
{
|
|
219
|
+
"node_types": [
|
|
220
|
+
{"id": "personType", "labels": ["Person"],
|
|
221
|
+
"properties": {"name": {"type": "STRING"}, "email": {"type": "STRING", "required": false}}},
|
|
222
|
+
{"id": "employeeType", "labels": ["Employee"], "supertypes": ["personType"],
|
|
223
|
+
"properties": {"email": {"type": "STRING", "required": true}, "salary": {"type": "FLOAT"}}},
|
|
224
|
+
{"id": "shareholderType", "labels": ["Shareholder"], "supertypes": ["personType"],
|
|
225
|
+
"properties": {"email": {"type": "STRING", "required": false}, "shares": {"type": "INTEGER"}}},
|
|
226
|
+
{"id": "managerType", "labels": ["Manager"], "supertypes": ["employeeType", "shareholderType"]},
|
|
227
|
+
{"id": "companyType", "labels": ["Company"], "properties": {"name": {"type": "STRING"}}}
|
|
228
|
+
],
|
|
229
|
+
"edge_types": [
|
|
230
|
+
{"id": "worksAtType", "labels": ["WORKS_AT"], "source": "employeeType", "target": "companyType",
|
|
231
|
+
"properties": {"since": {"type": "DATETIME"}},
|
|
232
|
+
"cardinality": {"out": "1..1", "in": "0..*"}},
|
|
233
|
+
{"id": "managesType", "labels": ["MANAGES"], "supertypes": ["worksAtType"], "source": "managerType",
|
|
234
|
+
"properties": {"since": {"type": "DATETIME", "required": false}}}
|
|
235
|
+
]
|
|
236
|
+
}
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
Here `managerType` has the labels Manager, Employee, Shareholder, and Person, and its `email` is
|
|
240
|
+
mandatory because `employeeType` is listed before `shareholderType`. `managesType` has the labels
|
|
241
|
+
MANAGES and WORKS_AT, an optional `since`, the cardinality of `worksAtType`, and the target
|
|
242
|
+
`companyType` inherited from `worksAtType`.
|
|
243
|
+
|
|
244
|
+
### Validation
|
|
245
|
+
|
|
246
|
+
`load_schema_json` raises `SchemaError` (a subclass of `ValueError`) for every violation, with the
|
|
247
|
+
location in the message, for example `edge_types[1] (id='managesType').source: unknown id 'x'`.
|
|
248
|
+
Besides the rules above, it rejects unknown keys (to catch typos) and an object that repeats a key.
|
|
249
|
+
The format is also defined as a JSON Schema (Draft 2020-12), shipped with the package as
|
|
250
|
+
`c2score/resources/schema.json`
|
|
251
|
+
([view](https://github.com/kde-salt/c2score/blob/main/src/c2score/resources/schema.json)).
|
|
252
|
+
|
|
253
|
+
## Parameters
|
|
254
|
+
|
|
255
|
+
```python
|
|
256
|
+
evaluate(instance, schema, *, label_weight=0.5, edge_weights=None,
|
|
257
|
+
redundancy_ratio=0.15, use_types=True, allow_any=True)
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
| Argument | Paper | Default | Allowed values |
|
|
261
|
+
| --- | --- | --- | --- |
|
|
262
|
+
| `label_weight` | α | `0.5` | A number in [0, 1]: the weight of labels against properties, for nodes and edges |
|
|
263
|
+
| `edge_weights` | (α_A, α_E, α_C) | `None` (automatic) | A tuple, list, or 1-D NumPy array of three numbers in [0, 1] summing to 1: the weights of the edge's own attributes, its endpoints, and its cardinality |
|
|
264
|
+
| `redundancy_ratio` | γ (replaces ε) | `0.15` | A finite number greater than 0. The larger it is, the more readily a definition counts as redundant |
|
|
265
|
+
| `use_types` | — | `True` | `True` or `False` (a NumPy boolean is accepted). See the table below |
|
|
266
|
+
| `allow_any` | — | `True` | `True` or `False`. See the table below |
|
|
267
|
+
|
|
268
|
+
`use_types` and `allow_any` decide how properties are compared. A property without `type` in the
|
|
269
|
+
schema has type `ANY`.
|
|
270
|
+
|
|
271
|
+
| `use_types` | `allow_any` | What is compared |
|
|
272
|
+
| --- | --- | --- |
|
|
273
|
+
| `True` | `True` (default) | Property names, mandatory or optional, and data types. `ANY` matches every type |
|
|
274
|
+
| `True` | `False` | The same, but `ANY` matches no type. Every property of the schema must have a type other than `ANY`; otherwise `evaluate` raises `SchemaError` |
|
|
275
|
+
| `False` | ignored | Property names and mandatory or optional only |
|
|
276
|
+
|
|
277
|
+
With `edge_weights=None`, the weights are (1/3, 1/3, 1/3) if any edge definition of the schema
|
|
278
|
+
writes a `cardinality`, and (0.5, 0.5, 0) otherwise. They are decided once, on the whole schema.
|
|
279
|
+
|
|
280
|
+
An argument out of range raises `ParameterError` (a subclass of `ValueError`).
|
|
281
|
+
|
|
282
|
+
The library names correspond to the paper as follows: `Instance` holds Abs(G), the types of the
|
|
283
|
+
instance; `Schema` is the candidate schema S; `cov_v`, `cov_e`, `con_v`, `con_e` are Cov_V, Cov_E,
|
|
284
|
+
Con_V, Con_E; `coverage` and `conciseness` are Cov and Con; `c2` is C2.
|
|
285
|
+
|
|
286
|
+
## Neo4j requirements
|
|
287
|
+
|
|
288
|
+
```python
|
|
289
|
+
load_neo4j(uri=None, *, auth=None, database=None, driver=None)
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
- Neo4j 5.13 or later. APOC is not needed.
|
|
293
|
+
- Connect with `uri` and `auth`, or pass an existing `neo4j.Driver` as `driver`. Choose the
|
|
294
|
+
database with `database`; if omitted, the server's default database is used.
|
|
295
|
+
- Only read access is needed. Load a graph that is not being written to.
|
|
296
|
+
- Failures (connection, authentication, queries, an older server) raise `InstanceLoadError`.
|
|
297
|
+
- Reading a large graph can take several minutes. Save the summary once, as in step 1 of the
|
|
298
|
+
quick start, and evaluate the saved file. The file also shows what was read from the graph.
|
|
299
|
+
|
|
300
|
+
## Notes
|
|
301
|
+
|
|
302
|
+
- A string passed to `load_schema_json` or `Instance.from_json` is always treated as a file
|
|
303
|
+
path, never as JSON text. To pass a schema that is already in memory, pass a `dict`
|
|
304
|
+
(for example the result of `json.loads`).
|
|
305
|
+
- A missing file raises `FileNotFoundError` (from `open`), not a c2score exception.
|
|
306
|
+
- `evaluate` raises `TypeError` if `instance` is not an `Instance` or `schema` is not a `Schema`.
|
|
307
|
+
- All exceptions raised by c2score derive from `C2ScoreError`: `SchemaError`, `InstanceLoadError`,
|
|
308
|
+
and `ParameterError`.
|
|
309
|
+
- Not included in this release: reading PG-Schema, loading an instance from a JSON graph, and
|
|
310
|
+
databases other than Neo4j.
|
|
311
|
+
|
|
312
|
+
## License
|
|
313
|
+
|
|
314
|
+
MIT. See [LICENSE](https://github.com/kde-salt/c2score/blob/main/LICENSE).
|