pyconvexity 0.1.0__tar.gz → 0.1.2__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.

Potentially problematic release.


This version of pyconvexity might be problematic. Click here for more details.

Files changed (26) hide show
  1. {pyconvexity-0.1.0 → pyconvexity-0.1.2}/PKG-INFO +1 -1
  2. {pyconvexity-0.1.0 → pyconvexity-0.1.2}/pyproject.toml +2 -2
  3. {pyconvexity-0.1.0 → pyconvexity-0.1.2}/src/pyconvexity/_version.py +1 -1
  4. {pyconvexity-0.1.0 → pyconvexity-0.1.2}/src/pyconvexity/core/database.py +20 -15
  5. pyconvexity-0.1.2/src/pyconvexity/data/schema/01_core_schema.sql +426 -0
  6. pyconvexity-0.1.2/src/pyconvexity/data/schema/02_data_metadata.sql +554 -0
  7. pyconvexity-0.1.2/src/pyconvexity/data/schema/03_validation_data.sql +443 -0
  8. pyconvexity-0.1.2/src/pyconvexity/data/schema/04_scenario_schema.sql +122 -0
  9. {pyconvexity-0.1.0 → pyconvexity-0.1.2}/src/pyconvexity/models/attributes.py +3 -1
  10. {pyconvexity-0.1.0 → pyconvexity-0.1.2}/src/pyconvexity.egg-info/PKG-INFO +1 -1
  11. {pyconvexity-0.1.0 → pyconvexity-0.1.2}/src/pyconvexity.egg-info/SOURCES.txt +4 -0
  12. {pyconvexity-0.1.0 → pyconvexity-0.1.2}/README.md +0 -0
  13. {pyconvexity-0.1.0 → pyconvexity-0.1.2}/setup.cfg +0 -0
  14. {pyconvexity-0.1.0 → pyconvexity-0.1.2}/src/pyconvexity/__init__.py +0 -0
  15. {pyconvexity-0.1.0 → pyconvexity-0.1.2}/src/pyconvexity/core/__init__.py +0 -0
  16. {pyconvexity-0.1.0 → pyconvexity-0.1.2}/src/pyconvexity/core/errors.py +0 -0
  17. {pyconvexity-0.1.0 → pyconvexity-0.1.2}/src/pyconvexity/core/types.py +0 -0
  18. {pyconvexity-0.1.0 → pyconvexity-0.1.2}/src/pyconvexity/models/__init__.py +0 -0
  19. {pyconvexity-0.1.0 → pyconvexity-0.1.2}/src/pyconvexity/models/components.py +0 -0
  20. {pyconvexity-0.1.0 → pyconvexity-0.1.2}/src/pyconvexity/models/network.py +0 -0
  21. {pyconvexity-0.1.0 → pyconvexity-0.1.2}/src/pyconvexity/validation/__init__.py +0 -0
  22. {pyconvexity-0.1.0 → pyconvexity-0.1.2}/src/pyconvexity/validation/rules.py +0 -0
  23. {pyconvexity-0.1.0 → pyconvexity-0.1.2}/src/pyconvexity.egg-info/dependency_links.txt +0 -0
  24. {pyconvexity-0.1.0 → pyconvexity-0.1.2}/src/pyconvexity.egg-info/requires.txt +0 -0
  25. {pyconvexity-0.1.0 → pyconvexity-0.1.2}/src/pyconvexity.egg-info/top_level.txt +0 -0
  26. {pyconvexity-0.1.0 → pyconvexity-0.1.2}/tests/test_core_types.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pyconvexity
3
- Version: 0.1.0
3
+ Version: 0.1.2
4
4
  Summary: Python library for energy system modeling and optimization with PyPSA
5
5
  Author-email: Convexity Team <info@convexity.com>
6
6
  License: MIT
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "pyconvexity"
7
- version = "0.1.0"
7
+ version = "0.1.2"
8
8
  description = "Python library for energy system modeling and optimization with PyPSA"
9
9
  readme = "README.md"
10
10
  license = {text = "MIT"}
@@ -66,7 +66,7 @@ where = ["src"]
66
66
 
67
67
 
68
68
  [tool.setuptools.package-data]
69
- pyconvexity = ["data/**/*", "schema/**/*"]
69
+ pyconvexity = ["data/**/*"]
70
70
 
71
71
  [tool.black]
72
72
  line-length = 100
@@ -1,2 +1,2 @@
1
1
  # This file is automatically updated by GitHub Actions during release
2
- __version__ = "0.1.0" # Default version for local development
2
+ __version__ = "0.1.2" # Default version for local development
@@ -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
- # Try bundled location first (PyInstaller)
209
- for p in sys.path:
210
- candidate = Path(p) / "schema"
211
- if candidate.exists() and candidate.is_dir():
212
- return candidate
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 main project
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 package data location
226
- try:
227
- import importlib.resources
228
- schema_path = importlib.resources.files('pyconvexity') / 'data' / 'schema'
229
- if schema_path.is_dir():
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');