ski-kg-validator 3.0.3__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.
- ski_kg_validator-3.0.3/PKG-INFO +105 -0
- ski_kg_validator-3.0.3/README.md +78 -0
- ski_kg_validator-3.0.3/pyproject.toml +45 -0
- ski_kg_validator-3.0.3/setup.cfg +4 -0
- ski_kg_validator-3.0.3/src/kg_validator/__init__.py +45 -0
- ski_kg_validator-3.0.3/src/kg_validator/cli.py +111 -0
- ski_kg_validator-3.0.3/src/kg_validator/loader.py +117 -0
- ski_kg_validator-3.0.3/src/kg_validator/models.py +247 -0
- ski_kg_validator-3.0.3/src/kg_validator/py.typed +0 -0
- ski_kg_validator-3.0.3/src/kg_validator/validator.py +238 -0
- ski_kg_validator-3.0.3/src/ski_kg_validator.egg-info/PKG-INFO +105 -0
- ski_kg_validator-3.0.3/src/ski_kg_validator.egg-info/SOURCES.txt +15 -0
- ski_kg_validator-3.0.3/src/ski_kg_validator.egg-info/dependency_links.txt +1 -0
- ski_kg_validator-3.0.3/src/ski_kg_validator.egg-info/entry_points.txt +2 -0
- ski_kg_validator-3.0.3/src/ski_kg_validator.egg-info/requires.txt +5 -0
- ski_kg_validator-3.0.3/src/ski_kg_validator.egg-info/top_level.txt +1 -0
- ski_kg_validator-3.0.3/tests/test_v3_validator.py +314 -0
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: ski-kg-validator
|
|
3
|
+
Version: 3.0.3
|
|
4
|
+
Summary: Validate and review extracted compliance rules with mandatory human expert oversight.
|
|
5
|
+
Author-email: KpiFinity <hello@kpifinity.com>
|
|
6
|
+
License-Expression: Apache-2.0
|
|
7
|
+
Project-URL: Homepage, https://github.com/kpifinity/ski-framework
|
|
8
|
+
Project-URL: Documentation, https://kpifinity.github.io/ski-framework/
|
|
9
|
+
Project-URL: Issues, https://github.com/kpifinity/ski-framework/issues
|
|
10
|
+
Keywords: ski-framework,compliance,knowledge-graph,validation
|
|
11
|
+
Classifier: Development Status :: 3 - Alpha
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: Intended Audience :: Legal Industry
|
|
14
|
+
Classifier: Operating System :: OS Independent
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
20
|
+
Requires-Python: >=3.10
|
|
21
|
+
Description-Content-Type: text/markdown
|
|
22
|
+
Requires-Dist: pydantic<3,>=2.13.4
|
|
23
|
+
Requires-Dist: click<9,>=8.1.7
|
|
24
|
+
Requires-Dist: python-dotenv<2,>=1.2.2
|
|
25
|
+
Requires-Dist: rapidfuzz<4,>=3.7
|
|
26
|
+
Requires-Dist: jinja2<4,>=3.1.6
|
|
27
|
+
|
|
28
|
+
# kg-validator
|
|
29
|
+
|
|
30
|
+
> Install from PyPI: `pip install ski-kg-validator` (publishing starts with the first release after June 2026).
|
|
31
|
+
|
|
32
|
+
> **Status:** v3.0 — first production-target release.
|
|
33
|
+
|
|
34
|
+
Validate v3 Knowledge Graphs against SKI Framework specification v3.0
|
|
35
|
+
§3 (schema) and §3.6 (cross-cutting validation passes). Phase 1
|
|
36
|
+
(compilation) tool.
|
|
37
|
+
|
|
38
|
+
## What it does
|
|
39
|
+
|
|
40
|
+
- **Schema validation.** Parses a v3 KG JSON file into the typed-node
|
|
41
|
+
Pydantic models. Unknown obligation types, unknown edge types,
|
|
42
|
+
unknown risk tiers, missing required fields, and extra unknown keys
|
|
43
|
+
are all rejected at load time.
|
|
44
|
+
- **Cross-cutting validation (§3.6).** Detects duplicate node IDs,
|
|
45
|
+
dangling edges, edges pointing at the wrong target node type, rules
|
|
46
|
+
without any obligation, and orphan obligations.
|
|
47
|
+
|
|
48
|
+
## What it no longer does (v2 retired in PR 10e)
|
|
49
|
+
|
|
50
|
+
- The v2 flat-rule-list format is gone. v3 typed obligations are the
|
|
51
|
+
only currency.
|
|
52
|
+
- Interactive `review`, `detect-conflicts`, `detect-duplicates`, and
|
|
53
|
+
HTML `report` subcommands were retired with the v2 path — they
|
|
54
|
+
worked on the flat-rule shape and have no v3 equivalent. v3
|
|
55
|
+
conflict and duplicate detection happen at the typed-obligation
|
|
56
|
+
level inside the validation passes and surface as issues in the
|
|
57
|
+
output JSON.
|
|
58
|
+
- The `ConfidenceLevel` enum is gone. The extractor now carries an
|
|
59
|
+
`ExtractionQuality` value (separate concept; see `kg-extractor`).
|
|
60
|
+
|
|
61
|
+
## Installation
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
pip install -e tools/kg-validator
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## Quick start
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
# Validate a v3 KG and print the issue summary:
|
|
71
|
+
kg-validator validate --input examples/energy/knowledge-graphs/kg-energy-v3-demo.json
|
|
72
|
+
|
|
73
|
+
# Validate and persist the full issue report:
|
|
74
|
+
kg-validator validate \
|
|
75
|
+
--input kg-energy-v3.json \
|
|
76
|
+
--output validation-report.json
|
|
77
|
+
|
|
78
|
+
# Show usage examples:
|
|
79
|
+
kg-validator examples
|
|
80
|
+
|
|
81
|
+
# Show version:
|
|
82
|
+
kg-validator version
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
The `validate` command exits with code 0 on a clean KG, code 2 on a
|
|
86
|
+
schema-level Pydantic validation failure, and code 3 on any CRITICAL
|
|
87
|
+
or HIGH severity §3.6 issue.
|
|
88
|
+
|
|
89
|
+
## Architecture
|
|
90
|
+
|
|
91
|
+
```
|
|
92
|
+
kg_validator/
|
|
93
|
+
├── __init__.py public API: KnowledgeGraphV3, V3Validator, load_v3_kg, models
|
|
94
|
+
├── loader.py KnowledgeGraphV3 container + load_v3_kg(path) entry point
|
|
95
|
+
├── models.py typed Pydantic models per spec v3.0 §3 (nodes, edges, enums)
|
|
96
|
+
├── validator.py V3Validator runs the §3.6 cross-cutting passes
|
|
97
|
+
└── cli.py click-based CLI (`validate`, `examples`, `version`)
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
## Conformance
|
|
101
|
+
|
|
102
|
+
The `examples/energy/knowledge-graphs/kg-energy-v3-demo.json` sample
|
|
103
|
+
KG must load cleanly and produce zero validation issues. This is
|
|
104
|
+
exercised by `tests/test_v3_validator.py` and by the CI durability
|
|
105
|
+
suite.
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
# kg-validator
|
|
2
|
+
|
|
3
|
+
> Install from PyPI: `pip install ski-kg-validator` (publishing starts with the first release after June 2026).
|
|
4
|
+
|
|
5
|
+
> **Status:** v3.0 — first production-target release.
|
|
6
|
+
|
|
7
|
+
Validate v3 Knowledge Graphs against SKI Framework specification v3.0
|
|
8
|
+
§3 (schema) and §3.6 (cross-cutting validation passes). Phase 1
|
|
9
|
+
(compilation) tool.
|
|
10
|
+
|
|
11
|
+
## What it does
|
|
12
|
+
|
|
13
|
+
- **Schema validation.** Parses a v3 KG JSON file into the typed-node
|
|
14
|
+
Pydantic models. Unknown obligation types, unknown edge types,
|
|
15
|
+
unknown risk tiers, missing required fields, and extra unknown keys
|
|
16
|
+
are all rejected at load time.
|
|
17
|
+
- **Cross-cutting validation (§3.6).** Detects duplicate node IDs,
|
|
18
|
+
dangling edges, edges pointing at the wrong target node type, rules
|
|
19
|
+
without any obligation, and orphan obligations.
|
|
20
|
+
|
|
21
|
+
## What it no longer does (v2 retired in PR 10e)
|
|
22
|
+
|
|
23
|
+
- The v2 flat-rule-list format is gone. v3 typed obligations are the
|
|
24
|
+
only currency.
|
|
25
|
+
- Interactive `review`, `detect-conflicts`, `detect-duplicates`, and
|
|
26
|
+
HTML `report` subcommands were retired with the v2 path — they
|
|
27
|
+
worked on the flat-rule shape and have no v3 equivalent. v3
|
|
28
|
+
conflict and duplicate detection happen at the typed-obligation
|
|
29
|
+
level inside the validation passes and surface as issues in the
|
|
30
|
+
output JSON.
|
|
31
|
+
- The `ConfidenceLevel` enum is gone. The extractor now carries an
|
|
32
|
+
`ExtractionQuality` value (separate concept; see `kg-extractor`).
|
|
33
|
+
|
|
34
|
+
## Installation
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
pip install -e tools/kg-validator
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Quick start
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
# Validate a v3 KG and print the issue summary:
|
|
44
|
+
kg-validator validate --input examples/energy/knowledge-graphs/kg-energy-v3-demo.json
|
|
45
|
+
|
|
46
|
+
# Validate and persist the full issue report:
|
|
47
|
+
kg-validator validate \
|
|
48
|
+
--input kg-energy-v3.json \
|
|
49
|
+
--output validation-report.json
|
|
50
|
+
|
|
51
|
+
# Show usage examples:
|
|
52
|
+
kg-validator examples
|
|
53
|
+
|
|
54
|
+
# Show version:
|
|
55
|
+
kg-validator version
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
The `validate` command exits with code 0 on a clean KG, code 2 on a
|
|
59
|
+
schema-level Pydantic validation failure, and code 3 on any CRITICAL
|
|
60
|
+
or HIGH severity §3.6 issue.
|
|
61
|
+
|
|
62
|
+
## Architecture
|
|
63
|
+
|
|
64
|
+
```
|
|
65
|
+
kg_validator/
|
|
66
|
+
├── __init__.py public API: KnowledgeGraphV3, V3Validator, load_v3_kg, models
|
|
67
|
+
├── loader.py KnowledgeGraphV3 container + load_v3_kg(path) entry point
|
|
68
|
+
├── models.py typed Pydantic models per spec v3.0 §3 (nodes, edges, enums)
|
|
69
|
+
├── validator.py V3Validator runs the §3.6 cross-cutting passes
|
|
70
|
+
└── cli.py click-based CLI (`validate`, `examples`, `version`)
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## Conformance
|
|
74
|
+
|
|
75
|
+
The `examples/energy/knowledge-graphs/kg-energy-v3-demo.json` sample
|
|
76
|
+
KG must load cleanly and produce zero validation issues. This is
|
|
77
|
+
exercised by `tests/test_v3_validator.py` and by the CI durability
|
|
78
|
+
suite.
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=68", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "ski-kg-validator"
|
|
7
|
+
version = "3.0.3"
|
|
8
|
+
description = "Validate and review extracted compliance rules with mandatory human expert oversight."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.10"
|
|
11
|
+
license = "Apache-2.0"
|
|
12
|
+
authors = [{ name = "KpiFinity", email = "hello@kpifinity.com" }]
|
|
13
|
+
keywords = ["ski-framework", "compliance", "knowledge-graph", "validation"]
|
|
14
|
+
classifiers = [
|
|
15
|
+
"Development Status :: 3 - Alpha",
|
|
16
|
+
"Intended Audience :: Developers",
|
|
17
|
+
"Intended Audience :: Legal Industry",
|
|
18
|
+
"Operating System :: OS Independent",
|
|
19
|
+
"Programming Language :: Python :: 3",
|
|
20
|
+
"Programming Language :: Python :: 3.10",
|
|
21
|
+
"Programming Language :: Python :: 3.11",
|
|
22
|
+
"Programming Language :: Python :: 3.12",
|
|
23
|
+
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
24
|
+
]
|
|
25
|
+
dependencies = [
|
|
26
|
+
"pydantic>=2.13.4,<3",
|
|
27
|
+
"click>=8.1.7,<9",
|
|
28
|
+
"python-dotenv>=1.2.2,<2",
|
|
29
|
+
"rapidfuzz>=3.7,<4",
|
|
30
|
+
"jinja2>=3.1.6,<4",
|
|
31
|
+
]
|
|
32
|
+
|
|
33
|
+
[project.urls]
|
|
34
|
+
Homepage = "https://github.com/kpifinity/ski-framework"
|
|
35
|
+
Documentation = "https://kpifinity.github.io/ski-framework/"
|
|
36
|
+
Issues = "https://github.com/kpifinity/ski-framework/issues"
|
|
37
|
+
|
|
38
|
+
[project.scripts]
|
|
39
|
+
kg-validator = "kg_validator.cli:main"
|
|
40
|
+
|
|
41
|
+
[tool.setuptools.packages.find]
|
|
42
|
+
where = ["src"]
|
|
43
|
+
|
|
44
|
+
[tool.setuptools.package-data]
|
|
45
|
+
kg_validator = ["py.typed"]
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
"""kg-validator — schema and §3.6 cross-cutting validation for v3 KGs."""
|
|
2
|
+
|
|
3
|
+
from .loader import KnowledgeGraphV3, load_v3_kg
|
|
4
|
+
from .models import (
|
|
5
|
+
Citation,
|
|
6
|
+
Definition,
|
|
7
|
+
Edge,
|
|
8
|
+
EdgeType,
|
|
9
|
+
Exemption,
|
|
10
|
+
Jurisdiction,
|
|
11
|
+
Obligation,
|
|
12
|
+
ObligationType,
|
|
13
|
+
Precedent,
|
|
14
|
+
RiskTier,
|
|
15
|
+
Rule,
|
|
16
|
+
Subject,
|
|
17
|
+
V3IssueType,
|
|
18
|
+
V3ValidationIssue,
|
|
19
|
+
V3ValidationResult,
|
|
20
|
+
)
|
|
21
|
+
from .validator import V3Validator
|
|
22
|
+
|
|
23
|
+
__version__ = "3.0.3"
|
|
24
|
+
__author__ = "KpiFinity"
|
|
25
|
+
|
|
26
|
+
__all__ = [
|
|
27
|
+
"Citation",
|
|
28
|
+
"Definition",
|
|
29
|
+
"Edge",
|
|
30
|
+
"EdgeType",
|
|
31
|
+
"Exemption",
|
|
32
|
+
"Jurisdiction",
|
|
33
|
+
"KnowledgeGraphV3",
|
|
34
|
+
"Obligation",
|
|
35
|
+
"ObligationType",
|
|
36
|
+
"Precedent",
|
|
37
|
+
"RiskTier",
|
|
38
|
+
"Rule",
|
|
39
|
+
"Subject",
|
|
40
|
+
"V3IssueType",
|
|
41
|
+
"V3ValidationIssue",
|
|
42
|
+
"V3ValidationResult",
|
|
43
|
+
"V3Validator",
|
|
44
|
+
"load_v3_kg",
|
|
45
|
+
]
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
"""Command-line interface for kg-validator (v3-only).
|
|
2
|
+
|
|
3
|
+
The validator now accepts a single subcommand — ``validate`` — which
|
|
4
|
+
loads a v3 KG JSON file and runs the spec §3.6 validation passes.
|
|
5
|
+
|
|
6
|
+
The v2 ``review``, ``detect-conflicts``, ``detect-duplicates``, and
|
|
7
|
+
HTML ``report`` subcommands were retired with PR 10e: they targeted
|
|
8
|
+
the flat-rule list shape that is no longer the framework's currency.
|
|
9
|
+
v3 conflict/duplicate detection happens at the typed-obligation level
|
|
10
|
+
inside :mod:`kg_validator.validator` and surfaces as issues in the
|
|
11
|
+
:class:`V3ValidationResult`.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
import json
|
|
17
|
+
from typing import Optional
|
|
18
|
+
|
|
19
|
+
import click
|
|
20
|
+
from pydantic import ValidationError
|
|
21
|
+
|
|
22
|
+
from . import __version__
|
|
23
|
+
from .loader import load_v3_kg
|
|
24
|
+
from .validator import V3Validator
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@click.group()
|
|
28
|
+
def main() -> None:
|
|
29
|
+
"""Validate v3 KG files against SKI Framework spec v3.0 §3."""
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
@main.command()
|
|
33
|
+
@click.option("--input", "-i", required=True, help="Input v3 KG JSON file.")
|
|
34
|
+
@click.option(
|
|
35
|
+
"--output",
|
|
36
|
+
"-o",
|
|
37
|
+
default=None,
|
|
38
|
+
help="Output JSON file with validation results. If omitted, results are printed to stdout when issues are found.",
|
|
39
|
+
)
|
|
40
|
+
def validate(input: str, output: Optional[str]) -> None:
|
|
41
|
+
"""Validate a v3 KG against spec §3 schema and §3.6 cross-cutting passes."""
|
|
42
|
+
try:
|
|
43
|
+
click.echo(f"Loading v3 KG from: {input}")
|
|
44
|
+
kg = load_v3_kg(input)
|
|
45
|
+
except FileNotFoundError as e:
|
|
46
|
+
click.echo(f"Error: {e!s}", err=True)
|
|
47
|
+
raise SystemExit(1) from e
|
|
48
|
+
except ValidationError as e:
|
|
49
|
+
click.echo("Schema validation failed:", err=True)
|
|
50
|
+
click.echo(str(e), err=True)
|
|
51
|
+
raise SystemExit(2) from e
|
|
52
|
+
|
|
53
|
+
click.echo(
|
|
54
|
+
f"Loaded {len(kg.nodes.rules)} rules, {len(kg.nodes.obligations)} obligations, {len(kg.edges)} edges"
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
result = V3Validator(kg).run()
|
|
58
|
+
|
|
59
|
+
click.echo("\nValidation complete:")
|
|
60
|
+
click.echo(f" Nodes: {result.total_nodes}")
|
|
61
|
+
click.echo(f" Edges: {result.total_edges}")
|
|
62
|
+
click.echo(f" Issues: {result.total_issues}")
|
|
63
|
+
if result.total_issues:
|
|
64
|
+
click.echo("\nIssues by type:")
|
|
65
|
+
by_type: dict[str, int] = {}
|
|
66
|
+
for issue in result.issues:
|
|
67
|
+
key = str(issue.issue_type)
|
|
68
|
+
by_type[key] = by_type.get(key, 0) + 1
|
|
69
|
+
for key in sorted(by_type):
|
|
70
|
+
click.echo(f" {key}: {by_type[key]}")
|
|
71
|
+
|
|
72
|
+
payload = result.model_dump(mode="json")
|
|
73
|
+
if output:
|
|
74
|
+
with open(output, "w", encoding="utf-8") as fh:
|
|
75
|
+
json.dump(payload, fh, indent=2)
|
|
76
|
+
click.echo(f"\nResults saved to: {output}")
|
|
77
|
+
elif result.total_issues:
|
|
78
|
+
click.echo("\n" + json.dumps(payload, indent=2))
|
|
79
|
+
|
|
80
|
+
# Exit non-zero on any CRITICAL or HIGH issue so CI flows can gate on it.
|
|
81
|
+
if not result.is_clean:
|
|
82
|
+
raise SystemExit(3)
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
@main.command()
|
|
86
|
+
def examples() -> None:
|
|
87
|
+
"""Show usage examples."""
|
|
88
|
+
examples_text = """
|
|
89
|
+
kg-validator examples (v3):
|
|
90
|
+
|
|
91
|
+
1. Validate a v3 KG file:
|
|
92
|
+
kg-validator validate --input kg-energy-v3.json
|
|
93
|
+
|
|
94
|
+
2. Validate and save the issue report:
|
|
95
|
+
kg-validator validate --input kg-energy-v3.json --output report.json
|
|
96
|
+
|
|
97
|
+
3. Compile-extract-validate flow:
|
|
98
|
+
kg-extractor extract --file regulation.txt --output kg-extracted.json --jurisdiction us.federal
|
|
99
|
+
kg-validator validate --input kg-extracted.json --output validation-report.json
|
|
100
|
+
"""
|
|
101
|
+
click.echo(examples_text)
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
@main.command()
|
|
105
|
+
def version() -> None:
|
|
106
|
+
"""Show version."""
|
|
107
|
+
click.echo(f"kg-validator {__version__}")
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
if __name__ == "__main__":
|
|
111
|
+
main()
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
"""Loader for v3 KG JSON files.
|
|
2
|
+
|
|
3
|
+
The on-disk v3 KG format is:
|
|
4
|
+
|
|
5
|
+
{
|
|
6
|
+
"metadata": {"name": "...", "schema_version": "3.0", ...},
|
|
7
|
+
"nodes": {
|
|
8
|
+
"subjects": [...], "rules": [...], "obligations": [...],
|
|
9
|
+
"definitions": [...], "exemptions": [...], "precedents": [...],
|
|
10
|
+
"jurisdictions": [...], "citations": [...]
|
|
11
|
+
},
|
|
12
|
+
"edges": [
|
|
13
|
+
{"type": "applies_to", "from": "...", "to": "..."},
|
|
14
|
+
...
|
|
15
|
+
]
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
Each node array contains objects matching the corresponding Pydantic
|
|
19
|
+
model in :mod:`.models`. Edges use ``from`` / ``to`` keys; the
|
|
20
|
+
:class:`Edge` model declares aliases so the keys can be used directly.
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
from __future__ import annotations
|
|
24
|
+
|
|
25
|
+
import json
|
|
26
|
+
from pathlib import Path
|
|
27
|
+
from typing import Any, Dict, List, Optional
|
|
28
|
+
|
|
29
|
+
from pydantic import BaseModel, ConfigDict, Field
|
|
30
|
+
|
|
31
|
+
from .models import (
|
|
32
|
+
Citation,
|
|
33
|
+
Definition,
|
|
34
|
+
Edge,
|
|
35
|
+
Exemption,
|
|
36
|
+
Jurisdiction,
|
|
37
|
+
Obligation,
|
|
38
|
+
Precedent,
|
|
39
|
+
Rule,
|
|
40
|
+
Subject,
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class KGV3Metadata(BaseModel):
|
|
45
|
+
"""KG-level metadata (informative; not used in validation passes)."""
|
|
46
|
+
|
|
47
|
+
name: str
|
|
48
|
+
schema_version: str = "3.0"
|
|
49
|
+
sector: Optional[str] = None
|
|
50
|
+
description: Optional[str] = None
|
|
51
|
+
compiled_at: Optional[str] = None
|
|
52
|
+
|
|
53
|
+
model_config = ConfigDict(extra="allow")
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
class KGV3Nodes(BaseModel):
|
|
57
|
+
"""Container for all node arrays. All keys default to empty lists."""
|
|
58
|
+
|
|
59
|
+
subjects: List[Subject] = Field(default_factory=list)
|
|
60
|
+
rules: List[Rule] = Field(default_factory=list)
|
|
61
|
+
obligations: List[Obligation] = Field(default_factory=list)
|
|
62
|
+
definitions: List[Definition] = Field(default_factory=list)
|
|
63
|
+
exemptions: List[Exemption] = Field(default_factory=list)
|
|
64
|
+
precedents: List[Precedent] = Field(default_factory=list)
|
|
65
|
+
jurisdictions: List[Jurisdiction] = Field(default_factory=list)
|
|
66
|
+
citations: List[Citation] = Field(default_factory=list)
|
|
67
|
+
|
|
68
|
+
model_config = ConfigDict(extra="forbid")
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
class KnowledgeGraphV3(BaseModel):
|
|
72
|
+
"""Top-level v3 KG container."""
|
|
73
|
+
|
|
74
|
+
metadata: KGV3Metadata
|
|
75
|
+
nodes: KGV3Nodes = Field(default_factory=KGV3Nodes)
|
|
76
|
+
edges: List[Edge] = Field(default_factory=list)
|
|
77
|
+
|
|
78
|
+
model_config = ConfigDict(extra="forbid")
|
|
79
|
+
|
|
80
|
+
def all_node_ids(self) -> Dict[str, str]:
|
|
81
|
+
"""Map every node id to its type name. Useful for edge validation."""
|
|
82
|
+
out: Dict[str, str] = {}
|
|
83
|
+
for n in self.nodes.subjects:
|
|
84
|
+
out[n.id] = "Subject"
|
|
85
|
+
for n in self.nodes.rules:
|
|
86
|
+
out[n.id] = "Rule"
|
|
87
|
+
for n in self.nodes.obligations:
|
|
88
|
+
out[n.id] = "Obligation"
|
|
89
|
+
for n in self.nodes.definitions:
|
|
90
|
+
out[n.id] = "Definition"
|
|
91
|
+
for n in self.nodes.exemptions:
|
|
92
|
+
out[n.id] = "Exemption"
|
|
93
|
+
for n in self.nodes.precedents:
|
|
94
|
+
out[n.id] = "Precedent"
|
|
95
|
+
for n in self.nodes.jurisdictions:
|
|
96
|
+
out[n.id] = "Jurisdiction"
|
|
97
|
+
for n in self.nodes.citations:
|
|
98
|
+
out[n.id] = "Citation"
|
|
99
|
+
return out
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def load_v3_kg(path: str) -> KnowledgeGraphV3:
|
|
103
|
+
"""Load and validate the Pydantic shape of a v3 KG JSON file.
|
|
104
|
+
|
|
105
|
+
Raises :class:`pydantic.ValidationError` if the file does not
|
|
106
|
+
conform to the schema. Returns the parsed :class:`KnowledgeGraphV3`
|
|
107
|
+
on success.
|
|
108
|
+
|
|
109
|
+
Schema-level validity is necessary but not sufficient. Run
|
|
110
|
+
:class:`kg_validator.V3Validator` against the loaded KG to
|
|
111
|
+
surface the cross-cutting issues from spec §3.6.
|
|
112
|
+
"""
|
|
113
|
+
text = Path(path).read_text(encoding="utf-8")
|
|
114
|
+
raw: Any = json.loads(text)
|
|
115
|
+
if not isinstance(raw, dict):
|
|
116
|
+
raise ValueError(f"Top-level JSON in {path} must be an object, got {type(raw).__name__}")
|
|
117
|
+
return KnowledgeGraphV3.model_validate(raw)
|