kuzu-grid-schema 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.
- kuzu_grid_schema-0.1.0/PKG-INFO +32 -0
- kuzu_grid_schema-0.1.0/README.md +22 -0
- kuzu_grid_schema-0.1.0/pyproject.toml +27 -0
- kuzu_grid_schema-0.1.0/setup.cfg +4 -0
- kuzu_grid_schema-0.1.0/src/kuzu_grid_schema/__init__.py +17 -0
- kuzu_grid_schema-0.1.0/src/kuzu_grid_schema/apply.py +56 -0
- kuzu_grid_schema-0.1.0/src/kuzu_grid_schema/cypher/__init__.py +5 -0
- kuzu_grid_schema-0.1.0/src/kuzu_grid_schema/cypher/tier0.py +413 -0
- kuzu_grid_schema-0.1.0/src/kuzu_grid_schema/cypher/tier1.py +171 -0
- kuzu_grid_schema-0.1.0/src/kuzu_grid_schema/cypher/tier2.py +258 -0
- kuzu_grid_schema-0.1.0/src/kuzu_grid_schema/cypher/tier3.py +209 -0
- kuzu_grid_schema-0.1.0/src/kuzu_grid_schema/effective_refresh.py +62 -0
- kuzu_grid_schema-0.1.0/src/kuzu_grid_schema/resources.py +31 -0
- kuzu_grid_schema-0.1.0/src/kuzu_grid_schema/schema/kuzu_grid_schema_composed.cypher +158 -0
- kuzu_grid_schema-0.1.0/src/kuzu_grid_schema/schema/schema_contract.json +227 -0
- kuzu_grid_schema-0.1.0/src/kuzu_grid_schema/schema/tiers/tier0.schema.json +69 -0
- kuzu_grid_schema-0.1.0/src/kuzu_grid_schema/schema/tiers/tier1.schema.json +26 -0
- kuzu_grid_schema-0.1.0/src/kuzu_grid_schema/schema/tiers/tier2.schema.json +61 -0
- kuzu_grid_schema-0.1.0/src/kuzu_grid_schema/schema/tiers/tier3.schema.json +44 -0
- kuzu_grid_schema-0.1.0/src/kuzu_grid_schema.egg-info/PKG-INFO +32 -0
- kuzu_grid_schema-0.1.0/src/kuzu_grid_schema.egg-info/SOURCES.txt +23 -0
- kuzu_grid_schema-0.1.0/src/kuzu_grid_schema.egg-info/dependency_links.txt +1 -0
- kuzu_grid_schema-0.1.0/src/kuzu_grid_schema.egg-info/requires.txt +4 -0
- kuzu_grid_schema-0.1.0/src/kuzu_grid_schema.egg-info/top_level.txt +1 -0
- kuzu_grid_schema-0.1.0/tests/test_schema_and_crud.py +136 -0
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: kuzu-grid-schema
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Shared Kuzu grid tier schemas, composed DDL, and per-tier Cypher CRUD
|
|
5
|
+
Requires-Python: >=3.10
|
|
6
|
+
Description-Content-Type: text/markdown
|
|
7
|
+
Requires-Dist: kuzu>=0.7.0
|
|
8
|
+
Provides-Extra: dev
|
|
9
|
+
Requires-Dist: pytest>=7.0; extra == "dev"
|
|
10
|
+
|
|
11
|
+
# kuzu-grid-schema
|
|
12
|
+
|
|
13
|
+
Pip-installable library that **owns** the Kuzu grid working-graph schema:
|
|
14
|
+
|
|
15
|
+
- Tier 0–3 JSON contracts
|
|
16
|
+
- One composed DDL for a **single** shared database
|
|
17
|
+
- Per-tier Cypher CRUD (write statements never cross tier ownership)
|
|
18
|
+
- `effective_*` refresh (`base → latest_actual → scenario → effective`)
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
pip install -e .
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
```python
|
|
25
|
+
from kuzu_grid_schema import apply_schema, open_database, refresh_effective
|
|
26
|
+
from kuzu_grid_schema.cypher import tier0, tier1, tier2, tier3
|
|
27
|
+
|
|
28
|
+
db, conn = open_database(":memory:")
|
|
29
|
+
apply_schema(conn)
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Other packages (including `kuzu-mcp`) should depend on this library via pip — do not copy DDL.
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# kuzu-grid-schema
|
|
2
|
+
|
|
3
|
+
Pip-installable library that **owns** the Kuzu grid working-graph schema:
|
|
4
|
+
|
|
5
|
+
- Tier 0–3 JSON contracts
|
|
6
|
+
- One composed DDL for a **single** shared database
|
|
7
|
+
- Per-tier Cypher CRUD (write statements never cross tier ownership)
|
|
8
|
+
- `effective_*` refresh (`base → latest_actual → scenario → effective`)
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
pip install -e .
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
```python
|
|
15
|
+
from kuzu_grid_schema import apply_schema, open_database, refresh_effective
|
|
16
|
+
from kuzu_grid_schema.cypher import tier0, tier1, tier2, tier3
|
|
17
|
+
|
|
18
|
+
db, conn = open_database(":memory:")
|
|
19
|
+
apply_schema(conn)
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
Other packages (including `kuzu-mcp`) should depend on this library via pip — do not copy DDL.
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=68", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "kuzu-grid-schema"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Shared Kuzu grid tier schemas, composed DDL, and per-tier Cypher CRUD"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.10"
|
|
11
|
+
dependencies = [
|
|
12
|
+
"kuzu>=0.7.0",
|
|
13
|
+
]
|
|
14
|
+
|
|
15
|
+
[project.optional-dependencies]
|
|
16
|
+
dev = ["pytest>=7.0"]
|
|
17
|
+
|
|
18
|
+
[tool.setuptools.packages.find]
|
|
19
|
+
where = ["src"]
|
|
20
|
+
include = ["kuzu_grid_schema*"]
|
|
21
|
+
|
|
22
|
+
[tool.setuptools.package-data]
|
|
23
|
+
kuzu_grid_schema = ["schema/**/*.json", "schema/**/*.cypher"]
|
|
24
|
+
|
|
25
|
+
[tool.pytest.ini_options]
|
|
26
|
+
testpaths = ["tests"]
|
|
27
|
+
pythonpath = ["src"]
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"""Shared Kuzu grid schema: contracts, DDL, per-tier Cypher, effective refresh."""
|
|
2
|
+
|
|
3
|
+
from kuzu_grid_schema.apply import apply_schema, open_database, query_rows
|
|
4
|
+
from kuzu_grid_schema.effective_refresh import refresh_effective
|
|
5
|
+
from kuzu_grid_schema.resources import load_composed_ddl, load_schema_contract, load_tier_schema
|
|
6
|
+
|
|
7
|
+
__all__ = [
|
|
8
|
+
"apply_schema",
|
|
9
|
+
"open_database",
|
|
10
|
+
"query_rows",
|
|
11
|
+
"refresh_effective",
|
|
12
|
+
"load_composed_ddl",
|
|
13
|
+
"load_schema_contract",
|
|
14
|
+
"load_tier_schema",
|
|
15
|
+
]
|
|
16
|
+
|
|
17
|
+
__version__ = "0.1.0"
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
"""Apply composed DDL and open a single shared Kuzu database."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
import kuzu
|
|
9
|
+
|
|
10
|
+
from kuzu_grid_schema.resources import load_composed_ddl
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def open_database(path: str | Path = ":memory:") -> tuple[kuzu.Database, kuzu.Connection]:
|
|
14
|
+
"""Open one Kuzu DB used by all tiers together."""
|
|
15
|
+
db = kuzu.Database(str(path))
|
|
16
|
+
conn = kuzu.Connection(db)
|
|
17
|
+
return db, conn
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def apply_schema(conn: kuzu.Connection, ddl: str | None = None) -> None:
|
|
21
|
+
"""Apply the composed DDL (tiers 0–3) to a single connection."""
|
|
22
|
+
text = ddl if ddl is not None else load_composed_ddl()
|
|
23
|
+
for statement in _split_cypher(text):
|
|
24
|
+
if statement.strip():
|
|
25
|
+
conn.execute(statement)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def query_rows(conn: kuzu.Connection, cypher: str, params: dict[str, Any] | None = None) -> list[dict[str, Any]]:
|
|
29
|
+
"""Return rows as dicts without requiring pandas/numpy."""
|
|
30
|
+
result = conn.execute(cypher, params or {})
|
|
31
|
+
columns = list(result.get_column_names())
|
|
32
|
+
rows: list[dict[str, Any]] = []
|
|
33
|
+
while result.has_next():
|
|
34
|
+
values = result.get_next()
|
|
35
|
+
rows.append(dict(zip(columns, values, strict=True)))
|
|
36
|
+
return rows
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def execute(conn: kuzu.Connection, cypher: str, params: dict[str, Any] | None = None) -> None:
|
|
40
|
+
conn.execute(cypher, params or {})
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def _split_cypher(text: str) -> list[str]:
|
|
44
|
+
statements: list[str] = []
|
|
45
|
+
current: list[str] = []
|
|
46
|
+
for line in text.splitlines():
|
|
47
|
+
stripped = line.strip()
|
|
48
|
+
if stripped.startswith("//"):
|
|
49
|
+
continue
|
|
50
|
+
current.append(line)
|
|
51
|
+
if stripped.endswith(";"):
|
|
52
|
+
statements.append("\n".join(current))
|
|
53
|
+
current = []
|
|
54
|
+
if current:
|
|
55
|
+
statements.append("\n".join(current))
|
|
56
|
+
return statements
|
|
@@ -0,0 +1,413 @@
|
|
|
1
|
+
"""Tier 0 Cypher CRUD: topology + base_* only (never latest_actual_/scenario_/effective_)."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
from kuzu_grid_schema.apply import execute, query_rows
|
|
8
|
+
|
|
9
|
+
# Forbidden prefixes in this module's write statements (enforced by tests).
|
|
10
|
+
FORBIDDEN_WRITE_PREFIXES = ("latest_actual_", "scenario_", "effective_", "dc_")
|
|
11
|
+
|
|
12
|
+
CREATE_BUS = """
|
|
13
|
+
CREATE (b:Bus {
|
|
14
|
+
bus_id: $bus_id,
|
|
15
|
+
name: $name,
|
|
16
|
+
base_kv: $base_kv,
|
|
17
|
+
bus_type: $bus_type,
|
|
18
|
+
zone: $zone,
|
|
19
|
+
in_service: $in_service,
|
|
20
|
+
is_slack: $is_slack,
|
|
21
|
+
base_in_service: $base_in_service,
|
|
22
|
+
geom: $geom
|
|
23
|
+
})
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
GET_BUS = """
|
|
27
|
+
MATCH (b:Bus {bus_id: $bus_id})
|
|
28
|
+
RETURN b.bus_id AS bus_id, b.name AS name, b.base_kv AS base_kv, b.bus_type AS bus_type,
|
|
29
|
+
b.zone AS zone, b.in_service AS in_service, b.is_slack AS is_slack,
|
|
30
|
+
b.base_in_service AS base_in_service, b.geom AS geom
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
LIST_BUS = """
|
|
34
|
+
MATCH (b:Bus)
|
|
35
|
+
RETURN b.bus_id AS bus_id, b.name AS name, b.base_kv AS base_kv, b.bus_type AS bus_type,
|
|
36
|
+
b.zone AS zone, b.in_service AS in_service, b.is_slack AS is_slack,
|
|
37
|
+
b.base_in_service AS base_in_service, b.geom AS geom
|
|
38
|
+
ORDER BY b.bus_id
|
|
39
|
+
"""
|
|
40
|
+
|
|
41
|
+
UPDATE_BUS = """
|
|
42
|
+
MATCH (b:Bus {bus_id: $bus_id})
|
|
43
|
+
SET b.name = coalesce($name, b.name),
|
|
44
|
+
b.base_kv = coalesce($base_kv, b.base_kv),
|
|
45
|
+
b.bus_type = coalesce($bus_type, b.bus_type),
|
|
46
|
+
b.zone = coalesce($zone, b.zone),
|
|
47
|
+
b.in_service = coalesce($in_service, b.in_service),
|
|
48
|
+
b.is_slack = coalesce($is_slack, b.is_slack),
|
|
49
|
+
b.base_in_service = coalesce($base_in_service, b.base_in_service),
|
|
50
|
+
b.geom = coalesce($geom, b.geom)
|
|
51
|
+
"""
|
|
52
|
+
|
|
53
|
+
DELETE_BUS = "MATCH (b:Bus {bus_id: $bus_id}) DETACH DELETE b"
|
|
54
|
+
|
|
55
|
+
CREATE_GENERATOR = """
|
|
56
|
+
CREATE (g:Generator {
|
|
57
|
+
gen_id: $gen_id,
|
|
58
|
+
fuel_type: $fuel_type,
|
|
59
|
+
control_mode: $control_mode,
|
|
60
|
+
pmax_mw: $pmax_mw,
|
|
61
|
+
pmin_mw: $pmin_mw,
|
|
62
|
+
base_p_mw: $base_p_mw,
|
|
63
|
+
q_mvar: $q_mvar,
|
|
64
|
+
vm_pu: $vm_pu,
|
|
65
|
+
base_in_service: $base_in_service
|
|
66
|
+
})
|
|
67
|
+
WITH g
|
|
68
|
+
MATCH (b:Bus {bus_id: $bus_id})
|
|
69
|
+
CREATE (g)-[:GENERATOR_AT_BUS]->(b)
|
|
70
|
+
"""
|
|
71
|
+
|
|
72
|
+
GET_GENERATOR = """
|
|
73
|
+
MATCH (g:Generator {gen_id: $gen_id})
|
|
74
|
+
OPTIONAL MATCH (g)-[:GENERATOR_AT_BUS]->(b:Bus)
|
|
75
|
+
RETURN g.gen_id AS gen_id, b.bus_id AS bus_id, g.fuel_type AS fuel_type,
|
|
76
|
+
g.control_mode AS control_mode, g.pmax_mw AS pmax_mw, g.pmin_mw AS pmin_mw,
|
|
77
|
+
g.base_p_mw AS base_p_mw, g.q_mvar AS q_mvar, g.vm_pu AS vm_pu,
|
|
78
|
+
g.base_in_service AS base_in_service
|
|
79
|
+
"""
|
|
80
|
+
|
|
81
|
+
LIST_GENERATOR = """
|
|
82
|
+
MATCH (g:Generator)
|
|
83
|
+
OPTIONAL MATCH (g)-[:GENERATOR_AT_BUS]->(b:Bus)
|
|
84
|
+
RETURN g.gen_id AS gen_id, b.bus_id AS bus_id, g.fuel_type AS fuel_type,
|
|
85
|
+
g.control_mode AS control_mode, g.pmax_mw AS pmax_mw, g.pmin_mw AS pmin_mw,
|
|
86
|
+
g.base_p_mw AS base_p_mw, g.q_mvar AS q_mvar, g.vm_pu AS vm_pu,
|
|
87
|
+
g.base_in_service AS base_in_service
|
|
88
|
+
ORDER BY g.gen_id
|
|
89
|
+
"""
|
|
90
|
+
|
|
91
|
+
UPDATE_GENERATOR = """
|
|
92
|
+
MATCH (g:Generator {gen_id: $gen_id})
|
|
93
|
+
SET g.fuel_type = coalesce($fuel_type, g.fuel_type),
|
|
94
|
+
g.control_mode = coalesce($control_mode, g.control_mode),
|
|
95
|
+
g.pmax_mw = coalesce($pmax_mw, g.pmax_mw),
|
|
96
|
+
g.pmin_mw = coalesce($pmin_mw, g.pmin_mw),
|
|
97
|
+
g.base_p_mw = coalesce($base_p_mw, g.base_p_mw),
|
|
98
|
+
g.q_mvar = coalesce($q_mvar, g.q_mvar),
|
|
99
|
+
g.vm_pu = coalesce($vm_pu, g.vm_pu),
|
|
100
|
+
g.base_in_service = coalesce($base_in_service, g.base_in_service)
|
|
101
|
+
"""
|
|
102
|
+
|
|
103
|
+
DELETE_GENERATOR = "MATCH (g:Generator {gen_id: $gen_id}) DETACH DELETE g"
|
|
104
|
+
|
|
105
|
+
CREATE_LOAD = """
|
|
106
|
+
CREATE (l:Load {
|
|
107
|
+
load_id: $load_id,
|
|
108
|
+
load_zone: $load_zone,
|
|
109
|
+
base_p_mw: $base_p_mw,
|
|
110
|
+
base_q_mvar: $base_q_mvar,
|
|
111
|
+
scaling: $scaling,
|
|
112
|
+
base_in_service: $base_in_service
|
|
113
|
+
})
|
|
114
|
+
WITH l
|
|
115
|
+
MATCH (b:Bus {bus_id: $bus_id})
|
|
116
|
+
CREATE (l)-[:LOAD_AT_BUS]->(b)
|
|
117
|
+
"""
|
|
118
|
+
|
|
119
|
+
GET_LOAD = """
|
|
120
|
+
MATCH (l:Load {load_id: $load_id})
|
|
121
|
+
OPTIONAL MATCH (l)-[:LOAD_AT_BUS]->(b:Bus)
|
|
122
|
+
RETURN l.load_id AS load_id, b.bus_id AS bus_id, l.load_zone AS load_zone,
|
|
123
|
+
l.base_p_mw AS base_p_mw, l.base_q_mvar AS base_q_mvar, l.scaling AS scaling,
|
|
124
|
+
l.base_in_service AS base_in_service
|
|
125
|
+
"""
|
|
126
|
+
|
|
127
|
+
LIST_LOAD = """
|
|
128
|
+
MATCH (l:Load)
|
|
129
|
+
OPTIONAL MATCH (l)-[:LOAD_AT_BUS]->(b:Bus)
|
|
130
|
+
RETURN l.load_id AS load_id, b.bus_id AS bus_id, l.load_zone AS load_zone,
|
|
131
|
+
l.base_p_mw AS base_p_mw, l.base_q_mvar AS base_q_mvar, l.scaling AS scaling,
|
|
132
|
+
l.base_in_service AS base_in_service
|
|
133
|
+
ORDER BY l.load_id
|
|
134
|
+
"""
|
|
135
|
+
|
|
136
|
+
UPDATE_LOAD = """
|
|
137
|
+
MATCH (l:Load {load_id: $load_id})
|
|
138
|
+
SET l.load_zone = coalesce($load_zone, l.load_zone),
|
|
139
|
+
l.base_p_mw = coalesce($base_p_mw, l.base_p_mw),
|
|
140
|
+
l.base_q_mvar = coalesce($base_q_mvar, l.base_q_mvar),
|
|
141
|
+
l.scaling = coalesce($scaling, l.scaling),
|
|
142
|
+
l.base_in_service = coalesce($base_in_service, l.base_in_service)
|
|
143
|
+
"""
|
|
144
|
+
|
|
145
|
+
DELETE_LOAD = "MATCH (l:Load {load_id: $load_id}) DETACH DELETE l"
|
|
146
|
+
|
|
147
|
+
CREATE_BRANCH = """
|
|
148
|
+
MATCH (a:Bus {bus_id: $from_bus_id}), (b:Bus {bus_id: $to_bus_id})
|
|
149
|
+
CREATE (a)-[e:CONNECTED_TO {
|
|
150
|
+
branch_id: $branch_id,
|
|
151
|
+
element_kind: $element_kind,
|
|
152
|
+
r_pu: $r_pu,
|
|
153
|
+
x_pu: $x_pu,
|
|
154
|
+
b_pu: $b_pu,
|
|
155
|
+
c_nf_per_km: $c_nf_per_km,
|
|
156
|
+
g_us_per_km: $g_us_per_km,
|
|
157
|
+
rate_a_mva: $rate_a_mva,
|
|
158
|
+
base_rate_a_mva: $base_rate_a_mva,
|
|
159
|
+
length_km: $length_km,
|
|
160
|
+
tap_ratio: $tap_ratio,
|
|
161
|
+
phase_shift_deg: $phase_shift_deg,
|
|
162
|
+
pfe_kw: $pfe_kw,
|
|
163
|
+
i0_percent: $i0_percent,
|
|
164
|
+
status: $status,
|
|
165
|
+
base_in_service: $base_in_service,
|
|
166
|
+
parallel: $parallel
|
|
167
|
+
}]->(b)
|
|
168
|
+
"""
|
|
169
|
+
|
|
170
|
+
GET_BRANCH = """
|
|
171
|
+
MATCH (a:Bus)-[e:CONNECTED_TO {branch_id: $branch_id}]->(b:Bus)
|
|
172
|
+
RETURN e.branch_id AS branch_id, a.bus_id AS from_bus_id, b.bus_id AS to_bus_id,
|
|
173
|
+
e.element_kind AS element_kind, e.r_pu AS r_pu, e.x_pu AS x_pu, e.b_pu AS b_pu,
|
|
174
|
+
e.rate_a_mva AS rate_a_mva, e.base_rate_a_mva AS base_rate_a_mva,
|
|
175
|
+
e.length_km AS length_km, e.status AS status, e.base_in_service AS base_in_service,
|
|
176
|
+
e.parallel AS parallel
|
|
177
|
+
"""
|
|
178
|
+
|
|
179
|
+
LIST_BRANCH = """
|
|
180
|
+
MATCH (a:Bus)-[e:CONNECTED_TO]->(b:Bus)
|
|
181
|
+
RETURN e.branch_id AS branch_id, a.bus_id AS from_bus_id, b.bus_id AS to_bus_id,
|
|
182
|
+
e.element_kind AS element_kind, e.r_pu AS r_pu, e.x_pu AS x_pu, e.b_pu AS b_pu,
|
|
183
|
+
e.rate_a_mva AS rate_a_mva, e.base_rate_a_mva AS base_rate_a_mva,
|
|
184
|
+
e.length_km AS length_km, e.status AS status, e.base_in_service AS base_in_service,
|
|
185
|
+
e.parallel AS parallel
|
|
186
|
+
ORDER BY e.branch_id
|
|
187
|
+
"""
|
|
188
|
+
|
|
189
|
+
UPDATE_BRANCH = """
|
|
190
|
+
MATCH ()-[e:CONNECTED_TO {branch_id: $branch_id}]->()
|
|
191
|
+
SET e.element_kind = coalesce($element_kind, e.element_kind),
|
|
192
|
+
e.r_pu = coalesce($r_pu, e.r_pu),
|
|
193
|
+
e.x_pu = coalesce($x_pu, e.x_pu),
|
|
194
|
+
e.b_pu = coalesce($b_pu, e.b_pu),
|
|
195
|
+
e.rate_a_mva = coalesce($rate_a_mva, e.rate_a_mva),
|
|
196
|
+
e.base_rate_a_mva = coalesce($base_rate_a_mva, e.base_rate_a_mva),
|
|
197
|
+
e.length_km = coalesce($length_km, e.length_km),
|
|
198
|
+
e.status = coalesce($status, e.status),
|
|
199
|
+
e.base_in_service = coalesce($base_in_service, e.base_in_service),
|
|
200
|
+
e.parallel = coalesce($parallel, e.parallel)
|
|
201
|
+
"""
|
|
202
|
+
|
|
203
|
+
DELETE_BRANCH = "MATCH ()-[e:CONNECTED_TO {branch_id: $branch_id}]->() DELETE e"
|
|
204
|
+
|
|
205
|
+
CREATE_BOUNDARY = """
|
|
206
|
+
CREATE (bi:BoundaryInjection {
|
|
207
|
+
boundary_id: $boundary_id,
|
|
208
|
+
p_mw: $p_mw,
|
|
209
|
+
q_mvar: $q_mvar,
|
|
210
|
+
source: $source
|
|
211
|
+
})
|
|
212
|
+
WITH bi
|
|
213
|
+
MATCH (b:Bus {bus_id: $bus_id})
|
|
214
|
+
CREATE (bi)-[:BOUNDARY_AT_BUS]->(b)
|
|
215
|
+
"""
|
|
216
|
+
|
|
217
|
+
GET_BOUNDARY = """
|
|
218
|
+
MATCH (bi:BoundaryInjection {boundary_id: $boundary_id})
|
|
219
|
+
OPTIONAL MATCH (bi)-[:BOUNDARY_AT_BUS]->(b:Bus)
|
|
220
|
+
RETURN bi.boundary_id AS boundary_id, b.bus_id AS bus_id, bi.p_mw AS p_mw,
|
|
221
|
+
bi.q_mvar AS q_mvar, bi.source AS source
|
|
222
|
+
"""
|
|
223
|
+
|
|
224
|
+
LIST_BOUNDARY = """
|
|
225
|
+
MATCH (bi:BoundaryInjection)
|
|
226
|
+
OPTIONAL MATCH (bi)-[:BOUNDARY_AT_BUS]->(b:Bus)
|
|
227
|
+
RETURN bi.boundary_id AS boundary_id, b.bus_id AS bus_id, bi.p_mw AS p_mw,
|
|
228
|
+
bi.q_mvar AS q_mvar, bi.source AS source
|
|
229
|
+
ORDER BY bi.boundary_id
|
|
230
|
+
"""
|
|
231
|
+
|
|
232
|
+
UPDATE_BOUNDARY = """
|
|
233
|
+
MATCH (bi:BoundaryInjection {boundary_id: $boundary_id})
|
|
234
|
+
SET bi.p_mw = coalesce($p_mw, bi.p_mw),
|
|
235
|
+
bi.q_mvar = coalesce($q_mvar, bi.q_mvar),
|
|
236
|
+
bi.source = coalesce($source, bi.source)
|
|
237
|
+
"""
|
|
238
|
+
|
|
239
|
+
DELETE_BOUNDARY = "MATCH (bi:BoundaryInjection {boundary_id: $boundary_id}) DETACH DELETE bi"
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
def create_bus(conn, **params: Any) -> None:
|
|
243
|
+
defaults = {
|
|
244
|
+
"name": params.get("bus_id"),
|
|
245
|
+
"bus_type": "b",
|
|
246
|
+
"zone": "",
|
|
247
|
+
"in_service": True,
|
|
248
|
+
"is_slack": False,
|
|
249
|
+
"base_in_service": True,
|
|
250
|
+
"geom": None,
|
|
251
|
+
"base_kv": 0.0,
|
|
252
|
+
}
|
|
253
|
+
defaults.update({k: v for k, v in params.items() if v is not None})
|
|
254
|
+
execute(conn, CREATE_BUS, defaults)
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
def get_bus(conn, bus_id: str) -> dict[str, Any] | None:
|
|
258
|
+
rows = query_rows(conn, GET_BUS, {"bus_id": bus_id})
|
|
259
|
+
return rows[0] if rows else None
|
|
260
|
+
|
|
261
|
+
|
|
262
|
+
def list_buses(conn) -> list[dict[str, Any]]:
|
|
263
|
+
return query_rows(conn, LIST_BUS)
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
def update_bus(conn, bus_id: str, **params: Any) -> None:
|
|
267
|
+
payload = {"bus_id": bus_id, "name": None, "base_kv": None, "bus_type": None, "zone": None,
|
|
268
|
+
"in_service": None, "is_slack": None, "base_in_service": None, "geom": None}
|
|
269
|
+
payload.update(params)
|
|
270
|
+
execute(conn, UPDATE_BUS, payload)
|
|
271
|
+
|
|
272
|
+
|
|
273
|
+
def delete_bus(conn, bus_id: str) -> None:
|
|
274
|
+
execute(conn, DELETE_BUS, {"bus_id": bus_id})
|
|
275
|
+
|
|
276
|
+
|
|
277
|
+
def create_generator(conn, **params: Any) -> None:
|
|
278
|
+
defaults = {
|
|
279
|
+
"fuel_type": "",
|
|
280
|
+
"control_mode": "PV",
|
|
281
|
+
"pmax_mw": 0.0,
|
|
282
|
+
"pmin_mw": 0.0,
|
|
283
|
+
"base_p_mw": 0.0,
|
|
284
|
+
"q_mvar": 0.0,
|
|
285
|
+
"vm_pu": 1.0,
|
|
286
|
+
"base_in_service": True,
|
|
287
|
+
}
|
|
288
|
+
defaults.update({k: v for k, v in params.items() if v is not None})
|
|
289
|
+
execute(conn, CREATE_GENERATOR, defaults)
|
|
290
|
+
|
|
291
|
+
|
|
292
|
+
def get_generator(conn, gen_id: str) -> dict[str, Any] | None:
|
|
293
|
+
rows = query_rows(conn, GET_GENERATOR, {"gen_id": gen_id})
|
|
294
|
+
return rows[0] if rows else None
|
|
295
|
+
|
|
296
|
+
|
|
297
|
+
def list_generators(conn) -> list[dict[str, Any]]:
|
|
298
|
+
return query_rows(conn, LIST_GENERATOR)
|
|
299
|
+
|
|
300
|
+
|
|
301
|
+
def update_generator(conn, gen_id: str, **params: Any) -> None:
|
|
302
|
+
payload = {"gen_id": gen_id, "fuel_type": None, "control_mode": None, "pmax_mw": None,
|
|
303
|
+
"pmin_mw": None, "base_p_mw": None, "q_mvar": None, "vm_pu": None,
|
|
304
|
+
"base_in_service": None}
|
|
305
|
+
payload.update(params)
|
|
306
|
+
execute(conn, UPDATE_GENERATOR, payload)
|
|
307
|
+
|
|
308
|
+
|
|
309
|
+
def delete_generator(conn, gen_id: str) -> None:
|
|
310
|
+
execute(conn, DELETE_GENERATOR, {"gen_id": gen_id})
|
|
311
|
+
|
|
312
|
+
|
|
313
|
+
def create_load(conn, **params: Any) -> None:
|
|
314
|
+
defaults = {
|
|
315
|
+
"load_zone": "",
|
|
316
|
+
"base_p_mw": 0.0,
|
|
317
|
+
"base_q_mvar": 0.0,
|
|
318
|
+
"scaling": 1.0,
|
|
319
|
+
"base_in_service": True,
|
|
320
|
+
}
|
|
321
|
+
defaults.update({k: v for k, v in params.items() if v is not None})
|
|
322
|
+
execute(conn, CREATE_LOAD, defaults)
|
|
323
|
+
|
|
324
|
+
|
|
325
|
+
def get_load(conn, load_id: str) -> dict[str, Any] | None:
|
|
326
|
+
rows = query_rows(conn, GET_LOAD, {"load_id": load_id})
|
|
327
|
+
return rows[0] if rows else None
|
|
328
|
+
|
|
329
|
+
|
|
330
|
+
def list_loads(conn) -> list[dict[str, Any]]:
|
|
331
|
+
return query_rows(conn, LIST_LOAD)
|
|
332
|
+
|
|
333
|
+
|
|
334
|
+
def update_load(conn, load_id: str, **params: Any) -> None:
|
|
335
|
+
payload = {"load_id": load_id, "load_zone": None, "base_p_mw": None, "base_q_mvar": None,
|
|
336
|
+
"scaling": None, "base_in_service": None}
|
|
337
|
+
payload.update(params)
|
|
338
|
+
execute(conn, UPDATE_LOAD, payload)
|
|
339
|
+
|
|
340
|
+
|
|
341
|
+
def delete_load(conn, load_id: str) -> None:
|
|
342
|
+
execute(conn, DELETE_LOAD, {"load_id": load_id})
|
|
343
|
+
|
|
344
|
+
|
|
345
|
+
def create_branch(conn, **params: Any) -> None:
|
|
346
|
+
defaults = {
|
|
347
|
+
"element_kind": "line",
|
|
348
|
+
"r_pu": 0.0,
|
|
349
|
+
"x_pu": 0.01,
|
|
350
|
+
"b_pu": 0.0,
|
|
351
|
+
"c_nf_per_km": 0.0,
|
|
352
|
+
"g_us_per_km": 0.0,
|
|
353
|
+
"rate_a_mva": None,
|
|
354
|
+
"base_rate_a_mva": None,
|
|
355
|
+
"length_km": 1.0,
|
|
356
|
+
"tap_ratio": 1.0,
|
|
357
|
+
"phase_shift_deg": 0.0,
|
|
358
|
+
"pfe_kw": 0.0,
|
|
359
|
+
"i0_percent": 0.0,
|
|
360
|
+
"status": True,
|
|
361
|
+
"base_in_service": True,
|
|
362
|
+
"parallel": 1,
|
|
363
|
+
}
|
|
364
|
+
defaults.update({k: v for k, v in params.items() if v is not None})
|
|
365
|
+
execute(conn, CREATE_BRANCH, defaults)
|
|
366
|
+
|
|
367
|
+
|
|
368
|
+
def get_branch(conn, branch_id: str) -> dict[str, Any] | None:
|
|
369
|
+
rows = query_rows(conn, GET_BRANCH, {"branch_id": branch_id})
|
|
370
|
+
return rows[0] if rows else None
|
|
371
|
+
|
|
372
|
+
|
|
373
|
+
def list_branches(conn) -> list[dict[str, Any]]:
|
|
374
|
+
return query_rows(conn, LIST_BRANCH)
|
|
375
|
+
|
|
376
|
+
|
|
377
|
+
def update_branch(conn, branch_id: str, **params: Any) -> None:
|
|
378
|
+
payload = {
|
|
379
|
+
"branch_id": branch_id, "element_kind": None, "r_pu": None, "x_pu": None, "b_pu": None,
|
|
380
|
+
"rate_a_mva": None, "base_rate_a_mva": None, "length_km": None, "status": None,
|
|
381
|
+
"base_in_service": None, "parallel": None,
|
|
382
|
+
}
|
|
383
|
+
payload.update(params)
|
|
384
|
+
execute(conn, UPDATE_BRANCH, payload)
|
|
385
|
+
|
|
386
|
+
|
|
387
|
+
def delete_branch(conn, branch_id: str) -> None:
|
|
388
|
+
execute(conn, DELETE_BRANCH, {"branch_id": branch_id})
|
|
389
|
+
|
|
390
|
+
|
|
391
|
+
def create_boundary(conn, **params: Any) -> None:
|
|
392
|
+
defaults = {"p_mw": 0.0, "q_mvar": 0.0, "source": "equivalent"}
|
|
393
|
+
defaults.update({k: v for k, v in params.items() if v is not None})
|
|
394
|
+
execute(conn, CREATE_BOUNDARY, defaults)
|
|
395
|
+
|
|
396
|
+
|
|
397
|
+
def get_boundary(conn, boundary_id: str) -> dict[str, Any] | None:
|
|
398
|
+
rows = query_rows(conn, GET_BOUNDARY, {"boundary_id": boundary_id})
|
|
399
|
+
return rows[0] if rows else None
|
|
400
|
+
|
|
401
|
+
|
|
402
|
+
def list_boundaries(conn) -> list[dict[str, Any]]:
|
|
403
|
+
return query_rows(conn, LIST_BOUNDARY)
|
|
404
|
+
|
|
405
|
+
|
|
406
|
+
def update_boundary(conn, boundary_id: str, **params: Any) -> None:
|
|
407
|
+
payload = {"boundary_id": boundary_id, "p_mw": None, "q_mvar": None, "source": None}
|
|
408
|
+
payload.update(params)
|
|
409
|
+
execute(conn, UPDATE_BOUNDARY, payload)
|
|
410
|
+
|
|
411
|
+
|
|
412
|
+
def delete_boundary(conn, boundary_id: str) -> None:
|
|
413
|
+
execute(conn, DELETE_BOUNDARY, {"boundary_id": boundary_id})
|