pyconvexity 0.1.0__py3-none-any.whl → 0.1.1__py3-none-any.whl
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.
Potentially problematic release.
This version of pyconvexity might be problematic. Click here for more details.
- pyconvexity/_version.py +1 -1
- pyconvexity/core/database.py +20 -15
- pyconvexity/data/schema/01_core_schema.sql +426 -0
- pyconvexity/data/schema/02_data_metadata.sql +554 -0
- pyconvexity/data/schema/03_validation_data.sql +443 -0
- pyconvexity/data/schema/04_scenario_schema.sql +122 -0
- {pyconvexity-0.1.0.dist-info → pyconvexity-0.1.1.dist-info}/METADATA +1 -1
- {pyconvexity-0.1.0.dist-info → pyconvexity-0.1.1.dist-info}/RECORD +10 -6
- {pyconvexity-0.1.0.dist-info → pyconvexity-0.1.1.dist-info}/WHEEL +0 -0
- {pyconvexity-0.1.0.dist-info → pyconvexity-0.1.1.dist-info}/top_level.txt +0 -0
pyconvexity/_version.py
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
# This file is automatically updated by GitHub Actions during release
|
|
2
|
-
__version__ = "0.1.
|
|
2
|
+
__version__ = "0.1.1" # Default version for local development
|
pyconvexity/core/database.py
CHANGED
|
@@ -204,17 +204,25 @@ def _find_schema_directory() -> Optional[Path]:
|
|
|
204
204
|
|
|
205
205
|
Returns:
|
|
206
206
|
Path to schema directory or None if not found
|
|
207
|
-
"""
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
if
|
|
212
|
-
return
|
|
207
|
+
""" # Try package data location first (PyPI/pip install)
|
|
208
|
+
try:
|
|
209
|
+
import importlib.resources
|
|
210
|
+
schema_path = importlib.resources.files('pyconvexity') / 'data' / 'schema'
|
|
211
|
+
if schema_path.is_dir():
|
|
212
|
+
return Path(str(schema_path))
|
|
213
|
+
except (ImportError, AttributeError):
|
|
214
|
+
pass
|
|
213
215
|
|
|
214
216
|
# Try relative to this file (development mode)
|
|
215
217
|
current_file = Path(__file__)
|
|
216
218
|
|
|
217
|
-
# Look for schema in the
|
|
219
|
+
# Look for schema in the package data directory
|
|
220
|
+
# pyconvexity/src/pyconvexity/core/database.py -> pyconvexity/src/pyconvexity/data/schema
|
|
221
|
+
package_schema_dir = current_file.parent.parent / "data" / "schema"
|
|
222
|
+
if package_schema_dir.exists():
|
|
223
|
+
return package_schema_dir
|
|
224
|
+
|
|
225
|
+
# Look for schema in the main project (development mode)
|
|
218
226
|
# Assuming pyconvexity/src/pyconvexity/core/database.py
|
|
219
227
|
# and schema is at project_root/schema
|
|
220
228
|
project_root = current_file.parent.parent.parent.parent.parent
|
|
@@ -222,14 +230,11 @@ def _find_schema_directory() -> Optional[Path]:
|
|
|
222
230
|
if dev_schema_dir.exists():
|
|
223
231
|
return dev_schema_dir
|
|
224
232
|
|
|
225
|
-
# Try
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
return Path(str(schema_path))
|
|
231
|
-
except (ImportError, AttributeError):
|
|
232
|
-
pass
|
|
233
|
+
# Try bundled location (PyInstaller)
|
|
234
|
+
for p in sys.path:
|
|
235
|
+
candidate = Path(p) / "schema"
|
|
236
|
+
if candidate.exists() and candidate.is_dir():
|
|
237
|
+
return candidate
|
|
233
238
|
|
|
234
239
|
return None
|
|
235
240
|
|
|
@@ -0,0 +1,426 @@
|
|
|
1
|
+
-- ============================================================================
|
|
2
|
+
-- CORE ENERGY NETWORK SCHEMA
|
|
3
|
+
-- Essential tables for networks, components, and attributes
|
|
4
|
+
-- Optimized for atomic database operations
|
|
5
|
+
-- Version 2.1.0
|
|
6
|
+
-- ============================================================================
|
|
7
|
+
|
|
8
|
+
-- ============================================================================
|
|
9
|
+
-- NETWORKS AND TIME MANAGEMENT
|
|
10
|
+
-- ============================================================================
|
|
11
|
+
|
|
12
|
+
-- Networks table - represents energy system models
|
|
13
|
+
CREATE TABLE networks (
|
|
14
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
15
|
+
name TEXT NOT NULL,
|
|
16
|
+
description TEXT,
|
|
17
|
+
|
|
18
|
+
-- Time axis definition (single source of truth)
|
|
19
|
+
time_start DATETIME NOT NULL,
|
|
20
|
+
time_end DATETIME NOT NULL,
|
|
21
|
+
time_interval TEXT NOT NULL, -- ISO 8601 duration (PT1H, PT30M, PT2H, etc.)
|
|
22
|
+
|
|
23
|
+
-- Unmet load flag
|
|
24
|
+
unmet_load_active BOOLEAN DEFAULT 1, -- 1 = true, 0 = false
|
|
25
|
+
|
|
26
|
+
-- Metadata
|
|
27
|
+
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
28
|
+
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
29
|
+
created_by TEXT,
|
|
30
|
+
|
|
31
|
+
CONSTRAINT valid_time_range CHECK (time_end > time_start)
|
|
32
|
+
);
|
|
33
|
+
|
|
34
|
+
CREATE INDEX idx_networks_name ON networks(name);
|
|
35
|
+
CREATE INDEX idx_networks_created_at ON networks(created_at);
|
|
36
|
+
|
|
37
|
+
-- Network time periods - computed from time axis definition
|
|
38
|
+
-- This table is populated automatically based on network time axis
|
|
39
|
+
CREATE TABLE network_time_periods (
|
|
40
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
41
|
+
network_id INTEGER NOT NULL,
|
|
42
|
+
timestamp DATETIME NOT NULL,
|
|
43
|
+
period_index INTEGER NOT NULL, -- 0-based index for array operations
|
|
44
|
+
|
|
45
|
+
CONSTRAINT fk_time_periods_network
|
|
46
|
+
FOREIGN KEY (network_id) REFERENCES networks(id) ON DELETE CASCADE,
|
|
47
|
+
CONSTRAINT uq_time_period_network_index
|
|
48
|
+
UNIQUE (network_id, period_index),
|
|
49
|
+
CONSTRAINT uq_time_period_network_timestamp
|
|
50
|
+
UNIQUE (network_id, timestamp)
|
|
51
|
+
);
|
|
52
|
+
|
|
53
|
+
CREATE INDEX idx_time_periods_network ON network_time_periods(network_id);
|
|
54
|
+
CREATE INDEX idx_time_periods_timestamp ON network_time_periods(timestamp);
|
|
55
|
+
CREATE INDEX idx_time_periods_index ON network_time_periods(network_id, period_index);
|
|
56
|
+
|
|
57
|
+
-- Network locks - prevents concurrent modifications
|
|
58
|
+
CREATE TABLE network_locks (
|
|
59
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
60
|
+
network_id INTEGER NOT NULL UNIQUE,
|
|
61
|
+
locked_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
62
|
+
locked_by TEXT NOT NULL, -- Process/user identifier
|
|
63
|
+
lock_reason TEXT NOT NULL, -- 'solving', 'importing', 'editing', etc.
|
|
64
|
+
|
|
65
|
+
CONSTRAINT fk_network_locks_network
|
|
66
|
+
FOREIGN KEY (network_id) REFERENCES networks(id) ON DELETE CASCADE
|
|
67
|
+
);
|
|
68
|
+
|
|
69
|
+
CREATE INDEX idx_network_locks_network ON network_locks(network_id);
|
|
70
|
+
CREATE INDEX idx_network_locks_reason ON network_locks(lock_reason);
|
|
71
|
+
|
|
72
|
+
-- ============================================================================
|
|
73
|
+
-- CARRIERS - ENERGY TYPES
|
|
74
|
+
-- ============================================================================
|
|
75
|
+
|
|
76
|
+
-- Carriers table - energy carriers (electricity, gas, heat, etc.)
|
|
77
|
+
CREATE TABLE carriers (
|
|
78
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
79
|
+
network_id INTEGER NOT NULL,
|
|
80
|
+
name TEXT NOT NULL,
|
|
81
|
+
|
|
82
|
+
-- Carrier properties from PyPSA reference
|
|
83
|
+
co2_emissions REAL DEFAULT 0.0, -- tonnes/MWh
|
|
84
|
+
color TEXT, -- Plotting color
|
|
85
|
+
nice_name TEXT, -- Display name
|
|
86
|
+
max_growth REAL DEFAULT NULL, -- MW - can be infinite
|
|
87
|
+
max_relative_growth REAL DEFAULT 0.0, -- MW
|
|
88
|
+
|
|
89
|
+
-- Metadata
|
|
90
|
+
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
91
|
+
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
92
|
+
|
|
93
|
+
CONSTRAINT fk_carriers_network
|
|
94
|
+
FOREIGN KEY (network_id) REFERENCES networks(id) ON DELETE CASCADE,
|
|
95
|
+
CONSTRAINT uq_carriers_network_name
|
|
96
|
+
UNIQUE (network_id, name)
|
|
97
|
+
);
|
|
98
|
+
|
|
99
|
+
CREATE INDEX idx_carriers_network ON carriers(network_id);
|
|
100
|
+
CREATE INDEX idx_carriers_name ON carriers(network_id, name);
|
|
101
|
+
|
|
102
|
+
-- ============================================================================
|
|
103
|
+
-- UNIFIED COMPONENT SYSTEM
|
|
104
|
+
-- ============================================================================
|
|
105
|
+
|
|
106
|
+
-- Components table - unified table for all network components
|
|
107
|
+
-- This is the single source of truth for component identity and relationships
|
|
108
|
+
CREATE TABLE components (
|
|
109
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
110
|
+
network_id INTEGER NOT NULL,
|
|
111
|
+
component_type TEXT NOT NULL, -- 'BUS', 'GENERATOR', 'LOAD', 'LINE', 'LINK', 'STORAGE_UNIT', 'STORE', 'UNMET_LOAD'
|
|
112
|
+
name TEXT NOT NULL,
|
|
113
|
+
|
|
114
|
+
-- Geographic location (optional - not all components have physical locations)
|
|
115
|
+
-- Lines and links are connections between buses and don't have their own coordinates
|
|
116
|
+
latitude REAL,
|
|
117
|
+
longitude REAL,
|
|
118
|
+
|
|
119
|
+
-- Energy carrier reference (NULL for CONSTRAINT components)
|
|
120
|
+
carrier_id INTEGER,
|
|
121
|
+
|
|
122
|
+
-- Bus connections - simple column approach
|
|
123
|
+
-- For single connections (GENERATOR, LOAD, STORAGE_UNIT, STORE): use bus_id
|
|
124
|
+
-- For dual connections (LINE, LINK): use bus0_id and bus1_id
|
|
125
|
+
-- For buses: all are NULL (buses don't connect to other buses)
|
|
126
|
+
bus_id INTEGER, -- Single bus connection
|
|
127
|
+
bus0_id INTEGER, -- First bus for lines/links
|
|
128
|
+
bus1_id INTEGER, -- Second bus for lines/links
|
|
129
|
+
|
|
130
|
+
-- Metadata
|
|
131
|
+
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
132
|
+
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
133
|
+
|
|
134
|
+
CONSTRAINT fk_components_network
|
|
135
|
+
FOREIGN KEY (network_id) REFERENCES networks(id) ON DELETE CASCADE,
|
|
136
|
+
CONSTRAINT fk_components_carrier
|
|
137
|
+
FOREIGN KEY (carrier_id) REFERENCES carriers(id),
|
|
138
|
+
CONSTRAINT fk_components_bus
|
|
139
|
+
FOREIGN KEY (bus_id) REFERENCES components(id),
|
|
140
|
+
CONSTRAINT fk_components_bus0
|
|
141
|
+
FOREIGN KEY (bus0_id) REFERENCES components(id),
|
|
142
|
+
CONSTRAINT fk_components_bus1
|
|
143
|
+
FOREIGN KEY (bus1_id) REFERENCES components(id),
|
|
144
|
+
CONSTRAINT uq_components_network_name
|
|
145
|
+
UNIQUE (network_id, name),
|
|
146
|
+
CONSTRAINT valid_component_type
|
|
147
|
+
CHECK (component_type IN ('BUS', 'GENERATOR', 'LOAD', 'LINE', 'LINK', 'STORAGE_UNIT', 'STORE', 'UNMET_LOAD', 'CONSTRAINT')),
|
|
148
|
+
-- New constraint: carrier_id must be NOT NULL for all components except CONSTRAINT
|
|
149
|
+
CONSTRAINT valid_carrier_id
|
|
150
|
+
CHECK (component_type != 'CONSTRAINT' OR carrier_id IS NULL)
|
|
151
|
+
-- Note: UNMET_LOAD uniqueness per bus is enforced in backend logic since SQLite doesn't support partial unique constraints
|
|
152
|
+
);
|
|
153
|
+
|
|
154
|
+
-- Optimized indexes for atomic operations
|
|
155
|
+
CREATE INDEX idx_components_network ON components(network_id);
|
|
156
|
+
CREATE INDEX idx_components_type ON components(component_type);
|
|
157
|
+
CREATE INDEX idx_components_name ON components(name);
|
|
158
|
+
CREATE INDEX idx_components_network_type ON components(network_id, component_type);
|
|
159
|
+
CREATE INDEX idx_components_carrier ON components(carrier_id);
|
|
160
|
+
CREATE INDEX idx_components_network_name ON components(network_id, name); -- For fast lookups
|
|
161
|
+
CREATE INDEX idx_components_location ON components(latitude, longitude); -- For spatial queries
|
|
162
|
+
CREATE INDEX idx_components_bus ON components(bus_id); -- For bus connections
|
|
163
|
+
CREATE INDEX idx_components_bus0 ON components(bus0_id); -- For line/link connections
|
|
164
|
+
CREATE INDEX idx_components_bus1 ON components(bus1_id); -- For line/link connections
|
|
165
|
+
|
|
166
|
+
-- ============================================================================
|
|
167
|
+
-- ATTRIBUTE VALIDATION SYSTEM
|
|
168
|
+
-- ============================================================================
|
|
169
|
+
|
|
170
|
+
-- Attribute validation rules table - defines what attributes are valid for each component type
|
|
171
|
+
-- This enforces PyPSA attribute definitions at the database level
|
|
172
|
+
CREATE TABLE attribute_validation_rules (
|
|
173
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
174
|
+
component_type TEXT NOT NULL,
|
|
175
|
+
attribute_name TEXT NOT NULL,
|
|
176
|
+
display_name TEXT, -- Human-readable display name
|
|
177
|
+
|
|
178
|
+
-- Validation rules from PyPSA reference
|
|
179
|
+
data_type TEXT NOT NULL, -- 'float', 'boolean', 'string', 'int'
|
|
180
|
+
unit TEXT, -- 'MW', 'per unit', 'currency/MWh', etc.
|
|
181
|
+
default_value TEXT, -- String representation of default
|
|
182
|
+
allowed_storage_types TEXT NOT NULL, -- 'static', 'timeseries', 'static_or_timeseries'
|
|
183
|
+
is_required BOOLEAN DEFAULT FALSE,
|
|
184
|
+
is_input BOOLEAN DEFAULT TRUE, -- TRUE for inputs, FALSE for outputs
|
|
185
|
+
description TEXT,
|
|
186
|
+
|
|
187
|
+
-- Validation constraints
|
|
188
|
+
min_value REAL, -- Optional minimum value constraint
|
|
189
|
+
max_value REAL, -- Optional maximum value constraint
|
|
190
|
+
allowed_values TEXT, -- JSON array of allowed values for string types
|
|
191
|
+
|
|
192
|
+
-- Attribute grouping and storage control
|
|
193
|
+
group_name TEXT DEFAULT 'other', -- Attribute group: 'basic', 'power', 'economics', 'control', 'other'
|
|
194
|
+
to_save BOOLEAN DEFAULT TRUE, -- Whether to save this attribute in solve results
|
|
195
|
+
|
|
196
|
+
-- Metadata
|
|
197
|
+
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
198
|
+
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
199
|
+
|
|
200
|
+
-- Constraints
|
|
201
|
+
CONSTRAINT uq_validation_rule
|
|
202
|
+
UNIQUE (component_type, attribute_name),
|
|
203
|
+
CONSTRAINT valid_component_type_validation
|
|
204
|
+
CHECK (component_type IN ('BUS', 'GENERATOR', 'LOAD', 'LINE', 'LINK', 'STORAGE_UNIT', 'STORE', 'UNMET_LOAD', 'CONSTRAINT')),
|
|
205
|
+
CONSTRAINT valid_data_type
|
|
206
|
+
CHECK (data_type IN ('float', 'boolean', 'string', 'int')),
|
|
207
|
+
CONSTRAINT valid_allowed_storage_types
|
|
208
|
+
CHECK (allowed_storage_types IN ('static', 'timeseries', 'static_or_timeseries')),
|
|
209
|
+
CONSTRAINT valid_group_name
|
|
210
|
+
CHECK (group_name IN ('basic', 'power', 'economics', 'control', 'other'))
|
|
211
|
+
);
|
|
212
|
+
|
|
213
|
+
-- Optimized indexes for validation lookups
|
|
214
|
+
CREATE INDEX idx_validation_component_type ON attribute_validation_rules(component_type);
|
|
215
|
+
CREATE INDEX idx_validation_attribute ON attribute_validation_rules(component_type, attribute_name);
|
|
216
|
+
CREATE INDEX idx_validation_input ON attribute_validation_rules(is_input);
|
|
217
|
+
CREATE INDEX idx_validation_required ON attribute_validation_rules(is_required);
|
|
218
|
+
|
|
219
|
+
-- ============================================================================
|
|
220
|
+
-- UNIFIED COMPONENT ATTRIBUTES
|
|
221
|
+
-- ============================================================================
|
|
222
|
+
|
|
223
|
+
-- Component attributes - unified storage for all component properties
|
|
224
|
+
-- This is the heart of our atomic attribute system with scenario support
|
|
225
|
+
CREATE TABLE component_attributes (
|
|
226
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
227
|
+
component_id INTEGER NOT NULL,
|
|
228
|
+
attribute_name TEXT NOT NULL,
|
|
229
|
+
|
|
230
|
+
-- Scenario support - references scenarios table (master scenario has explicit ID)
|
|
231
|
+
scenario_id INTEGER NOT NULL,
|
|
232
|
+
|
|
233
|
+
-- Storage type - determines how value is stored
|
|
234
|
+
storage_type TEXT NOT NULL CHECK (storage_type IN ('static', 'timeseries')),
|
|
235
|
+
|
|
236
|
+
-- Unified value storage - exactly one must be non-NULL based on storage_type
|
|
237
|
+
static_value TEXT, -- JSON-encoded static value (handles all data types)
|
|
238
|
+
timeseries_data BLOB, -- Parquet format for timeseries
|
|
239
|
+
|
|
240
|
+
-- Cached metadata for performance
|
|
241
|
+
data_type TEXT NOT NULL, -- Copied from validation rules for fast access
|
|
242
|
+
unit TEXT, -- Copied from validation rules for fast access
|
|
243
|
+
is_input BOOLEAN DEFAULT TRUE, -- Copied from validation rules for fast access
|
|
244
|
+
|
|
245
|
+
-- Metadata
|
|
246
|
+
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
247
|
+
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
248
|
+
|
|
249
|
+
-- Constraints
|
|
250
|
+
CONSTRAINT fk_attributes_component
|
|
251
|
+
FOREIGN KEY (component_id) REFERENCES components(id) ON DELETE CASCADE,
|
|
252
|
+
CONSTRAINT fk_attributes_scenario
|
|
253
|
+
FOREIGN KEY (scenario_id) REFERENCES scenarios(id) ON DELETE CASCADE,
|
|
254
|
+
|
|
255
|
+
-- Ensure exactly one storage type is used
|
|
256
|
+
CONSTRAINT check_exactly_one_storage_type CHECK (
|
|
257
|
+
(storage_type = 'static' AND static_value IS NOT NULL AND timeseries_data IS NULL) OR
|
|
258
|
+
(storage_type = 'timeseries' AND static_value IS NULL AND timeseries_data IS NOT NULL)
|
|
259
|
+
),
|
|
260
|
+
|
|
261
|
+
-- Ensure unique attribute per component per scenario
|
|
262
|
+
CONSTRAINT uq_component_attribute_scenario
|
|
263
|
+
UNIQUE (component_id, attribute_name, scenario_id)
|
|
264
|
+
);
|
|
265
|
+
|
|
266
|
+
-- Highly optimized indexes for atomic operations with scenario support
|
|
267
|
+
CREATE INDEX idx_attributes_component ON component_attributes(component_id);
|
|
268
|
+
CREATE INDEX idx_attributes_name ON component_attributes(attribute_name);
|
|
269
|
+
CREATE INDEX idx_attributes_storage_type ON component_attributes(storage_type);
|
|
270
|
+
CREATE INDEX idx_attributes_component_name ON component_attributes(component_id, attribute_name);
|
|
271
|
+
CREATE INDEX idx_attributes_data_type ON component_attributes(data_type);
|
|
272
|
+
CREATE INDEX idx_attributes_is_input ON component_attributes(is_input);
|
|
273
|
+
CREATE INDEX idx_attributes_scenario ON component_attributes(scenario_id);
|
|
274
|
+
|
|
275
|
+
-- Composite indexes for common query patterns
|
|
276
|
+
CREATE INDEX idx_attributes_component_input ON component_attributes(component_id, is_input);
|
|
277
|
+
CREATE INDEX idx_attributes_component_storage ON component_attributes(component_id, storage_type);
|
|
278
|
+
CREATE INDEX idx_attributes_component_scenario ON component_attributes(component_id, scenario_id);
|
|
279
|
+
|
|
280
|
+
-- ============================================================================
|
|
281
|
+
-- VALIDATION TRIGGERS
|
|
282
|
+
-- ============================================================================
|
|
283
|
+
|
|
284
|
+
-- Trigger to validate attributes against rules on insert
|
|
285
|
+
CREATE TRIGGER validate_component_attribute_insert
|
|
286
|
+
BEFORE INSERT ON component_attributes
|
|
287
|
+
FOR EACH ROW
|
|
288
|
+
WHEN NOT EXISTS (
|
|
289
|
+
SELECT 1 FROM components c
|
|
290
|
+
JOIN attribute_validation_rules avr ON c.component_type = avr.component_type
|
|
291
|
+
WHERE c.id = NEW.component_id
|
|
292
|
+
AND avr.attribute_name = NEW.attribute_name
|
|
293
|
+
)
|
|
294
|
+
BEGIN
|
|
295
|
+
SELECT RAISE(ABORT, 'Attribute is not defined for this component type');
|
|
296
|
+
END;
|
|
297
|
+
|
|
298
|
+
-- Trigger to validate storage type on insert
|
|
299
|
+
CREATE TRIGGER validate_storage_type_insert
|
|
300
|
+
BEFORE INSERT ON component_attributes
|
|
301
|
+
FOR EACH ROW
|
|
302
|
+
WHEN EXISTS (
|
|
303
|
+
SELECT 1 FROM components c
|
|
304
|
+
JOIN attribute_validation_rules avr ON c.component_type = avr.component_type
|
|
305
|
+
WHERE c.id = NEW.component_id
|
|
306
|
+
AND avr.attribute_name = NEW.attribute_name
|
|
307
|
+
AND avr.allowed_storage_types != 'static_or_timeseries'
|
|
308
|
+
AND avr.allowed_storage_types != NEW.storage_type
|
|
309
|
+
)
|
|
310
|
+
BEGIN
|
|
311
|
+
SELECT RAISE(ABORT, 'Storage type not allowed for this attribute');
|
|
312
|
+
END;
|
|
313
|
+
|
|
314
|
+
-- Trigger to validate attributes against rules on update
|
|
315
|
+
CREATE TRIGGER validate_component_attribute_update
|
|
316
|
+
BEFORE UPDATE ON component_attributes
|
|
317
|
+
FOR EACH ROW
|
|
318
|
+
WHEN NOT EXISTS (
|
|
319
|
+
SELECT 1 FROM components c
|
|
320
|
+
JOIN attribute_validation_rules avr ON c.component_type = avr.component_type
|
|
321
|
+
WHERE c.id = NEW.component_id
|
|
322
|
+
AND avr.attribute_name = NEW.attribute_name
|
|
323
|
+
)
|
|
324
|
+
BEGIN
|
|
325
|
+
SELECT RAISE(ABORT, 'Attribute is not defined for this component type');
|
|
326
|
+
END;
|
|
327
|
+
|
|
328
|
+
-- Trigger to validate storage type on update
|
|
329
|
+
CREATE TRIGGER validate_storage_type_update
|
|
330
|
+
BEFORE UPDATE ON component_attributes
|
|
331
|
+
FOR EACH ROW
|
|
332
|
+
WHEN EXISTS (
|
|
333
|
+
SELECT 1 FROM components c
|
|
334
|
+
JOIN attribute_validation_rules avr ON c.component_type = avr.component_type
|
|
335
|
+
WHERE c.id = NEW.component_id
|
|
336
|
+
AND avr.attribute_name = NEW.attribute_name
|
|
337
|
+
AND avr.allowed_storage_types != 'static_or_timeseries'
|
|
338
|
+
AND avr.allowed_storage_types != NEW.storage_type
|
|
339
|
+
)
|
|
340
|
+
BEGIN
|
|
341
|
+
SELECT RAISE(ABORT, 'Storage type not allowed for this attribute');
|
|
342
|
+
END;
|
|
343
|
+
|
|
344
|
+
-- Trigger to update metadata timestamps
|
|
345
|
+
CREATE TRIGGER update_component_attributes_timestamp
|
|
346
|
+
BEFORE UPDATE ON component_attributes
|
|
347
|
+
FOR EACH ROW
|
|
348
|
+
BEGIN
|
|
349
|
+
UPDATE component_attributes
|
|
350
|
+
SET updated_at = CURRENT_TIMESTAMP
|
|
351
|
+
WHERE id = NEW.id;
|
|
352
|
+
END;
|
|
353
|
+
|
|
354
|
+
-- Trigger to update component timestamps when attributes change
|
|
355
|
+
CREATE TRIGGER update_component_timestamp_on_attribute_change
|
|
356
|
+
AFTER INSERT ON component_attributes
|
|
357
|
+
FOR EACH ROW
|
|
358
|
+
BEGIN
|
|
359
|
+
UPDATE components
|
|
360
|
+
SET updated_at = CURRENT_TIMESTAMP
|
|
361
|
+
WHERE id = NEW.component_id;
|
|
362
|
+
END;
|
|
363
|
+
|
|
364
|
+
-- ============================================================================
|
|
365
|
+
-- NETWORK CONFIGURATION
|
|
366
|
+
-- ============================================================================
|
|
367
|
+
|
|
368
|
+
-- Network configuration parameters - flexible parameter storage
|
|
369
|
+
-- Supports scenario-aware configuration with fallback to network defaults
|
|
370
|
+
CREATE TABLE network_config (
|
|
371
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
372
|
+
network_id INTEGER NOT NULL,
|
|
373
|
+
scenario_id INTEGER, -- NULL for network defaults
|
|
374
|
+
|
|
375
|
+
-- Parameter definition
|
|
376
|
+
param_name TEXT NOT NULL,
|
|
377
|
+
param_type TEXT NOT NULL,
|
|
378
|
+
param_value TEXT NOT NULL,
|
|
379
|
+
param_description TEXT,
|
|
380
|
+
|
|
381
|
+
-- Metadata
|
|
382
|
+
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
383
|
+
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
384
|
+
|
|
385
|
+
CONSTRAINT fk_network_config_network
|
|
386
|
+
FOREIGN KEY (network_id) REFERENCES networks(id) ON DELETE CASCADE,
|
|
387
|
+
CONSTRAINT fk_network_config_scenario
|
|
388
|
+
FOREIGN KEY (scenario_id) REFERENCES scenarios(id) ON DELETE CASCADE,
|
|
389
|
+
CONSTRAINT uq_network_config_param
|
|
390
|
+
UNIQUE (network_id, scenario_id, param_name),
|
|
391
|
+
CONSTRAINT valid_param_type
|
|
392
|
+
CHECK (param_type IN ('boolean', 'real', 'integer', 'string', 'json'))
|
|
393
|
+
);
|
|
394
|
+
|
|
395
|
+
-- Indexes for performance
|
|
396
|
+
CREATE INDEX idx_network_config_network ON network_config(network_id);
|
|
397
|
+
CREATE INDEX idx_network_config_scenario ON network_config(scenario_id);
|
|
398
|
+
CREATE INDEX idx_network_config_param ON network_config(param_name);
|
|
399
|
+
CREATE INDEX idx_network_config_network_param ON network_config(network_id, param_name);
|
|
400
|
+
CREATE INDEX idx_network_config_network_scenario_param ON network_config(network_id, scenario_id, param_name);
|
|
401
|
+
|
|
402
|
+
-- ============================================================================
|
|
403
|
+
-- SYSTEM METADATA
|
|
404
|
+
-- ============================================================================
|
|
405
|
+
|
|
406
|
+
-- System metadata table for schema versioning and configuration
|
|
407
|
+
CREATE TABLE system_metadata (
|
|
408
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
409
|
+
key TEXT NOT NULL UNIQUE,
|
|
410
|
+
value TEXT NOT NULL,
|
|
411
|
+
description TEXT,
|
|
412
|
+
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
413
|
+
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
|
414
|
+
);
|
|
415
|
+
|
|
416
|
+
CREATE INDEX idx_system_metadata_key ON system_metadata(key);
|
|
417
|
+
|
|
418
|
+
-- Insert initial schema version
|
|
419
|
+
INSERT INTO system_metadata (key, value, description)
|
|
420
|
+
VALUES ('schema_version', '2.4.0', 'Database schema version - Added network_config table for flexible parameter storage');
|
|
421
|
+
|
|
422
|
+
INSERT INTO system_metadata (key, value, description)
|
|
423
|
+
VALUES ('created_at', datetime('now'), 'Database creation timestamp');
|
|
424
|
+
|
|
425
|
+
INSERT INTO system_metadata (key, value, description)
|
|
426
|
+
VALUES ('atomic_operations', 'enabled', 'Atomic database operations support');
|