pyconvexity 0.3.8.post7__py3-none-any.whl → 0.4.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.
Files changed (48) hide show
  1. pyconvexity/__init__.py +87 -46
  2. pyconvexity/_version.py +1 -1
  3. pyconvexity/core/__init__.py +3 -5
  4. pyconvexity/core/database.py +111 -103
  5. pyconvexity/core/errors.py +16 -10
  6. pyconvexity/core/types.py +61 -54
  7. pyconvexity/data/__init__.py +0 -1
  8. pyconvexity/data/loaders/cache.py +65 -64
  9. pyconvexity/data/schema/01_core_schema.sql +134 -234
  10. pyconvexity/data/schema/02_data_metadata.sql +38 -168
  11. pyconvexity/data/schema/03_validation_data.sql +327 -264
  12. pyconvexity/data/sources/gem.py +169 -139
  13. pyconvexity/io/__init__.py +4 -10
  14. pyconvexity/io/excel_exporter.py +694 -480
  15. pyconvexity/io/excel_importer.py +817 -545
  16. pyconvexity/io/netcdf_exporter.py +66 -61
  17. pyconvexity/io/netcdf_importer.py +850 -619
  18. pyconvexity/models/__init__.py +109 -59
  19. pyconvexity/models/attributes.py +197 -178
  20. pyconvexity/models/carriers.py +70 -67
  21. pyconvexity/models/components.py +260 -236
  22. pyconvexity/models/network.py +202 -284
  23. pyconvexity/models/results.py +65 -55
  24. pyconvexity/models/scenarios.py +58 -88
  25. pyconvexity/solvers/__init__.py +5 -5
  26. pyconvexity/solvers/pypsa/__init__.py +3 -3
  27. pyconvexity/solvers/pypsa/api.py +150 -134
  28. pyconvexity/solvers/pypsa/batch_loader.py +165 -162
  29. pyconvexity/solvers/pypsa/builder.py +390 -291
  30. pyconvexity/solvers/pypsa/constraints.py +184 -162
  31. pyconvexity/solvers/pypsa/solver.py +968 -666
  32. pyconvexity/solvers/pypsa/storage.py +1377 -671
  33. pyconvexity/timeseries.py +63 -60
  34. pyconvexity/validation/__init__.py +14 -6
  35. pyconvexity/validation/rules.py +95 -84
  36. pyconvexity-0.4.1.dist-info/METADATA +46 -0
  37. pyconvexity-0.4.1.dist-info/RECORD +42 -0
  38. pyconvexity/data/__pycache__/__init__.cpython-313.pyc +0 -0
  39. pyconvexity/data/loaders/__pycache__/__init__.cpython-313.pyc +0 -0
  40. pyconvexity/data/loaders/__pycache__/cache.cpython-313.pyc +0 -0
  41. pyconvexity/data/schema/04_scenario_schema.sql +0 -122
  42. pyconvexity/data/schema/migrate_add_geometries.sql +0 -73
  43. pyconvexity/data/sources/__pycache__/__init__.cpython-313.pyc +0 -0
  44. pyconvexity/data/sources/__pycache__/gem.cpython-313.pyc +0 -0
  45. pyconvexity-0.3.8.post7.dist-info/METADATA +0 -138
  46. pyconvexity-0.3.8.post7.dist-info/RECORD +0 -49
  47. {pyconvexity-0.3.8.post7.dist-info → pyconvexity-0.4.1.dist-info}/WHEEL +0 -0
  48. {pyconvexity-0.3.8.post7.dist-info → pyconvexity-0.4.1.dist-info}/top_level.txt +0 -0
@@ -1,17 +1,16 @@
1
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
2
+ -- CORE ENERGY NETWORK SCHEMA (SIMPLIFIED)
3
+ -- Single-network-per-file design for desktop SQLite
4
+ -- Optimized for fast timeseries access and simple Rust/Python API
5
+ -- Version 3.1.0 - Single network + Sparse scenarios + Raw timeseries
6
6
  -- ============================================================================
7
7
 
8
8
  -- ============================================================================
9
- -- NETWORKS AND TIME MANAGEMENT
9
+ -- NETWORK METADATA
10
10
  -- ============================================================================
11
11
 
12
- -- Networks table - represents energy system models
13
- CREATE TABLE networks (
14
- id INTEGER PRIMARY KEY AUTOINCREMENT,
12
+ -- Network metadata - single row per database file
13
+ CREATE TABLE network_metadata (
15
14
  name TEXT NOT NULL,
16
15
  description TEXT,
17
16
 
@@ -20,55 +19,26 @@ CREATE TABLE networks (
20
19
  time_end DATETIME NOT NULL,
21
20
  time_interval TEXT NOT NULL, -- ISO 8601 duration (PT1H, PT30M, PT2H, etc.)
22
21
 
23
- -- Unmet load flag
24
- unmet_load_active BOOLEAN DEFAULT 1, -- 1 = true, 0 = false
22
+ -- Network-level flags
23
+ locked BOOLEAN DEFAULT 0, -- Prevent accidental edits to base network
25
24
 
26
25
  -- Metadata
27
26
  created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
28
27
  updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
29
- created_by TEXT,
30
28
 
31
29
  CONSTRAINT valid_time_range CHECK (time_end > time_start)
32
30
  );
33
31
 
34
- CREATE INDEX idx_networks_name ON networks(name);
35
- CREATE INDEX idx_networks_created_at ON networks(created_at);
36
-
37
32
  -- Network time periods - optimized storage using computed timestamps
38
- -- Instead of storing 75k+ timestamp strings, we compute them from the time axis
39
- -- This reduces storage from ~3.4MB to ~24 bytes per network
40
33
  CREATE TABLE network_time_periods (
41
- network_id INTEGER NOT NULL,
42
34
  period_count INTEGER NOT NULL, -- Total number of periods (e.g., 8760 for hourly year)
43
35
  start_timestamp INTEGER NOT NULL, -- Unix timestamp of first period
44
36
  interval_seconds INTEGER NOT NULL, -- Seconds between periods (3600 for hourly)
45
37
 
46
- PRIMARY KEY (network_id),
47
-
48
- CONSTRAINT fk_time_periods_network
49
- FOREIGN KEY (network_id) REFERENCES networks(id) ON DELETE CASCADE,
50
38
  CONSTRAINT valid_period_count CHECK (period_count > 0),
51
39
  CONSTRAINT valid_interval CHECK (interval_seconds > 0)
52
40
  );
53
41
 
54
- -- No additional indexes needed - primary key on network_id is sufficient
55
- -- Timestamps are computed as: start_timestamp + (period_index * interval_seconds)
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
42
  -- ============================================================================
73
43
  -- CARRIERS - ENERGY TYPES
74
44
  -- ============================================================================
@@ -76,8 +46,7 @@ CREATE INDEX idx_network_locks_reason ON network_locks(lock_reason);
76
46
  -- Carriers table - energy carriers (electricity, gas, heat, etc.)
77
47
  CREATE TABLE carriers (
78
48
  id INTEGER PRIMARY KEY AUTOINCREMENT,
79
- network_id INTEGER NOT NULL,
80
- name TEXT NOT NULL,
49
+ name TEXT NOT NULL UNIQUE,
81
50
 
82
51
  -- Carrier properties from PyPSA reference
83
52
  co2_emissions REAL DEFAULT 0.0, -- tonnes/MWh
@@ -85,44 +54,32 @@ CREATE TABLE carriers (
85
54
  nice_name TEXT, -- Display name
86
55
  max_growth REAL DEFAULT NULL, -- MW - can be infinite
87
56
  max_relative_growth REAL DEFAULT 0.0, -- MW
57
+ curtailable BOOLEAN DEFAULT FALSE, -- Whether the carrier can be curtailed
88
58
 
89
59
  -- Metadata
90
60
  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)
61
+ updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
97
62
  );
98
63
 
99
- CREATE INDEX idx_carriers_network ON carriers(network_id);
100
- CREATE INDEX idx_carriers_name ON carriers(network_id, name);
101
-
102
64
  -- ============================================================================
103
65
  -- UNIFIED COMPONENT SYSTEM
104
66
  -- ============================================================================
105
67
 
106
68
  -- Components table - unified table for all network components
107
- -- This is the single source of truth for component identity and relationships
108
69
  CREATE TABLE components (
109
70
  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,
71
+ component_type TEXT NOT NULL, -- 'BUS', 'GENERATOR', 'LOAD', 'LINE', 'LINK', 'STORAGE_UNIT', 'STORE', 'UNMET_LOAD', 'CONSTRAINT', 'TRANSFORMER', 'SHUNT_IMPEDANCE'
72
+ name TEXT NOT NULL UNIQUE,
113
73
 
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
74
+ -- Geographic location (optional)
116
75
  latitude REAL,
117
76
  longitude REAL,
77
+ geometry TEXT, -- GeoJSON geometry (Point, LineString, Polygon, etc.)
118
78
 
119
- -- Energy carrier reference (NULL for CONSTRAINT components)
79
+ -- Energy carrier reference
120
80
  carrier_id INTEGER,
121
81
 
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)
82
+ -- Bus connections
126
83
  bus_id INTEGER, -- Single bus connection
127
84
  bus0_id INTEGER, -- First bus for lines/links
128
85
  bus1_id INTEGER, -- Second bus for lines/links
@@ -131,8 +88,6 @@ CREATE TABLE components (
131
88
  created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
132
89
  updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
133
90
 
134
- CONSTRAINT fk_components_network
135
- FOREIGN KEY (network_id) REFERENCES networks(id) ON DELETE CASCADE,
136
91
  CONSTRAINT fk_components_carrier
137
92
  FOREIGN KEY (carrier_id) REFERENCES carriers(id),
138
93
  CONSTRAINT fk_components_bus
@@ -141,141 +96,178 @@ CREATE TABLE components (
141
96
  FOREIGN KEY (bus0_id) REFERENCES components(id),
142
97
  CONSTRAINT fk_components_bus1
143
98
  FOREIGN KEY (bus1_id) REFERENCES components(id),
144
- CONSTRAINT uq_components_network_name
145
- UNIQUE (network_id, name),
146
99
  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
100
+ CHECK (component_type IN ('BUS', 'GENERATOR', 'LOAD', 'LINE', 'LINK', 'STORAGE_UNIT', 'STORE', 'UNMET_LOAD', 'CONSTRAINT', 'TRANSFORMER', 'SHUNT_IMPEDANCE'))
152
101
  );
153
102
 
154
- -- Optimized indexes for atomic operations
155
- CREATE INDEX idx_components_network ON components(network_id);
103
+ -- Essential indexes only
156
104
  CREATE INDEX idx_components_type ON components(component_type);
157
105
  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
106
+ CREATE INDEX idx_components_bus ON components(bus_id);
107
+ CREATE INDEX idx_components_bus0 ON components(bus0_id);
108
+ CREATE INDEX idx_components_bus1 ON components(bus1_id);
165
109
 
166
110
  -- ============================================================================
167
111
  -- ATTRIBUTE VALIDATION SYSTEM
168
112
  -- ============================================================================
169
113
 
170
- -- Attribute validation rules table - defines what attributes are valid for each component type
171
- -- This enforces PyPSA attribute definitions at the database level
114
+ -- Attribute validation rules table
172
115
  CREATE TABLE attribute_validation_rules (
173
116
  id INTEGER PRIMARY KEY AUTOINCREMENT,
174
117
  component_type TEXT NOT NULL,
175
118
  attribute_name TEXT NOT NULL,
176
- display_name TEXT, -- Human-readable display name
119
+ display_name TEXT,
177
120
 
178
- -- Validation rules from PyPSA reference
121
+ -- Validation rules
179
122
  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
123
+ unit TEXT,
124
+ default_value TEXT,
182
125
  allowed_storage_types TEXT NOT NULL, -- 'static', 'timeseries', 'static_or_timeseries'
183
126
  is_required BOOLEAN DEFAULT FALSE,
184
- is_input BOOLEAN DEFAULT TRUE, -- TRUE for inputs, FALSE for outputs
127
+ is_input BOOLEAN DEFAULT TRUE,
185
128
  description TEXT,
186
129
 
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
130
+ -- Constraints
131
+ min_value REAL,
132
+ max_value REAL,
133
+ allowed_values TEXT, -- JSON array
195
134
 
196
- -- Metadata
197
- created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
198
- updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
135
+ -- Grouping
136
+ group_name TEXT DEFAULT 'other',
137
+ to_save BOOLEAN DEFAULT TRUE,
199
138
 
200
- -- Constraints
201
139
  CONSTRAINT uq_validation_rule
202
140
  UNIQUE (component_type, attribute_name),
203
141
  CONSTRAINT valid_component_type_validation
204
- CHECK (component_type IN ('BUS', 'GENERATOR', 'LOAD', 'LINE', 'LINK', 'STORAGE_UNIT', 'STORE', 'UNMET_LOAD', 'CONSTRAINT')),
142
+ CHECK (component_type IN ('BUS', 'GENERATOR', 'LOAD', 'LINE', 'LINK', 'STORAGE_UNIT', 'STORE', 'UNMET_LOAD', 'CONSTRAINT', 'TRANSFORMER', 'SHUNT_IMPEDANCE')),
205
143
  CONSTRAINT valid_data_type
206
144
  CHECK (data_type IN ('float', 'boolean', 'string', 'int')),
207
145
  CONSTRAINT valid_allowed_storage_types
208
146
  CHECK (allowed_storage_types IN ('static', 'timeseries', 'static_or_timeseries')),
209
147
  CONSTRAINT valid_group_name
210
- CHECK (group_name IN ('basic', 'power', 'economics', 'control', 'other'))
148
+ CHECK (group_name IN ('basic', 'capacity', 'power_limits', 'energy', 'unit_commitment', 'ramping', 'costs', 'electrical'))
211
149
  );
212
150
 
213
- -- Optimized indexes for validation lookups
151
+ -- Essential indexes only
214
152
  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);
153
+ CREATE INDEX idx_validation_lookup ON attribute_validation_rules(component_type, attribute_name);
218
154
 
219
155
  -- ============================================================================
220
- -- UNIFIED COMPONENT ATTRIBUTES
156
+ -- SCENARIOS - SPARSE OVERRIDE APPROACH
221
157
  -- ============================================================================
222
158
 
223
- -- Component attributes - unified storage for all component properties
224
- -- This is the heart of our atomic attribute system with scenario support
159
+ -- Scenarios table - represents alternative scenarios
160
+ -- Base network has NO scenario (scenario_id = NULL in attributes)
161
+ -- Supports both deterministic what-if scenarios (probability = NULL) and stochastic scenarios (probability set)
162
+ CREATE TABLE scenarios (
163
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
164
+ name TEXT NOT NULL UNIQUE,
165
+ description TEXT,
166
+ probability REAL DEFAULT NULL, -- For stochastic optimization (NULL = deterministic what-if)
167
+ created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
168
+
169
+ CONSTRAINT valid_probability
170
+ CHECK (probability IS NULL OR (probability >= 0 AND probability <= 1))
171
+ );
172
+
173
+ -- ============================================================================
174
+ -- UNIFIED COMPONENT ATTRIBUTES - SPARSE SCENARIOS + RAW TIMESERIES
175
+ -- ============================================================================
176
+
177
+ -- Component attributes - sparse scenario overrides
178
+ -- scenario_id = NULL → Base network (editable)
179
+ -- scenario_id = 1 → Scenario 1 (overrides base, read-only)
225
180
  CREATE TABLE component_attributes (
226
181
  id INTEGER PRIMARY KEY AUTOINCREMENT,
227
182
  component_id INTEGER NOT NULL,
228
183
  attribute_name TEXT NOT NULL,
229
184
 
230
- -- Scenario support - references scenarios table (master scenario has explicit ID)
231
- scenario_id INTEGER NOT NULL,
185
+ -- Scenario support - NULL = base network, non-NULL = scenario override
186
+ scenario_id INTEGER, -- NULLABLE!
232
187
 
233
- -- Storage type - determines how value is stored
188
+ -- Storage type
234
189
  storage_type TEXT NOT NULL CHECK (storage_type IN ('static', 'timeseries')),
235
190
 
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
191
+ -- Value storage
192
+ static_value TEXT, -- JSON-encoded static value (all data types)
193
+ timeseries_data BLOB, -- Raw f32 array (NOT Parquet!)
239
194
 
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
195
+ -- Cached metadata
196
+ data_type TEXT NOT NULL,
197
+ unit TEXT,
198
+ is_input BOOLEAN DEFAULT TRUE,
244
199
 
245
200
  -- Metadata
246
201
  created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
247
202
  updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
248
203
 
249
- -- Constraints
250
204
  CONSTRAINT fk_attributes_component
251
205
  FOREIGN KEY (component_id) REFERENCES components(id) ON DELETE CASCADE,
252
206
  CONSTRAINT fk_attributes_scenario
253
207
  FOREIGN KEY (scenario_id) REFERENCES scenarios(id) ON DELETE CASCADE,
254
208
 
255
- -- Ensure exactly one storage type is used
209
+ -- Storage validation
256
210
  CONSTRAINT check_exactly_one_storage_type CHECK (
257
211
  (storage_type = 'static' AND static_value IS NOT NULL AND timeseries_data IS NULL) OR
258
212
  (storage_type = 'timeseries' AND static_value IS NULL AND timeseries_data IS NOT NULL)
259
213
  ),
260
214
 
261
- -- Ensure unique attribute per component per scenario
215
+ -- Unique per component/attribute/scenario (NULL scenario counts as unique value)
262
216
  CONSTRAINT uq_component_attribute_scenario
263
217
  UNIQUE (component_id, attribute_name, scenario_id)
264
218
  );
265
219
 
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);
220
+ -- Essential indexes only
221
+ CREATE INDEX idx_attributes_lookup ON component_attributes(
222
+ component_id, attribute_name, scenario_id
223
+ );
273
224
  CREATE INDEX idx_attributes_scenario ON component_attributes(scenario_id);
274
225
 
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);
226
+ -- ============================================================================
227
+ -- SCENARIO CACHE - MATERIALIZED VIEW FOR FAST SCENARIO COUNTING
228
+ -- ============================================================================
229
+
230
+ -- Scenario cache - stores precomputed scenario counts and details
231
+ -- This is a materialized view that's kept in sync via application code
232
+ -- Updated transactionally whenever attribute values change
233
+ CREATE TABLE IF NOT EXISTS attribute_scenario_cache (
234
+ component_id INTEGER NOT NULL,
235
+ attribute_name TEXT NOT NULL,
236
+
237
+ -- Cached computed values
238
+ scenario_count INTEGER NOT NULL DEFAULT 1, -- Display count (includes synthetic base)
239
+ has_base_value BOOLEAN NOT NULL DEFAULT FALSE, -- TRUE if base network has value
240
+ has_scenario_values BOOLEAN NOT NULL DEFAULT FALSE, -- TRUE if any scenarios have values
241
+
242
+ -- Scenario details for dropdown (JSON array)
243
+ -- Format: [{scenario_id: 0, scenario_name: "Base", value: "123", has_value: true}, ...]
244
+ scenario_details TEXT,
245
+
246
+ -- Metadata
247
+ last_updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
248
+
249
+ PRIMARY KEY (component_id, attribute_name),
250
+ CONSTRAINT fk_scenario_cache_component
251
+ FOREIGN KEY (component_id) REFERENCES components(id) ON DELETE CASCADE
252
+ );
253
+
254
+ -- Index for fast component-based lookups
255
+ CREATE INDEX IF NOT EXISTS idx_scenario_cache_component
256
+ ON attribute_scenario_cache(component_id);
257
+
258
+ -- Index for bulk table loads
259
+ CREATE INDEX IF NOT EXISTS idx_scenario_cache_component_type
260
+ ON attribute_scenario_cache(component_id, attribute_name);
261
+
262
+ -- NOTES:
263
+ -- This cache is maintained by application code (Rust) in the same transaction
264
+ -- as attribute writes. This ensures ACID guarantees - the cache can never be
265
+ -- stale or inconsistent with the actual data.
266
+ --
267
+ -- Cache logic:
268
+ -- - If has_scenario_values: scenario_count = num_scenarios + 1 (always include base)
269
+ -- - If only has_base_value: scenario_count = 1 (no indicator shown)
270
+ -- - If neither: scenario_count = 0 (no values at all)
279
271
 
280
272
  -- ============================================================================
281
273
  -- VALIDATION TRIGGERS
@@ -311,37 +303,7 @@ BEGIN
311
303
  SELECT RAISE(ABORT, 'Storage type not allowed for this attribute');
312
304
  END;
313
305
 
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
306
+ -- Trigger to update timestamps
345
307
  CREATE TRIGGER update_component_attributes_timestamp
346
308
  BEFORE UPDATE ON component_attributes
347
309
  FOR EACH ROW
@@ -361,100 +323,38 @@ BEGIN
361
323
  WHERE id = NEW.component_id;
362
324
  END;
363
325
 
364
- -- ============================================================================
365
- -- COMPONENT GEOMETRIES - Optional GeoJSON geometries for spatial representation
366
- -- ============================================================================
367
-
368
- -- Component geometries - stores optional GeoJSON geometries for components
369
- -- Enables real spatial representation (e.g., actual line routes, generator footprints)
370
- CREATE TABLE component_geometries (
371
- id INTEGER PRIMARY KEY AUTOINCREMENT,
372
- component_id INTEGER NOT NULL UNIQUE,
373
-
374
- -- GeoJSON geometry stored as JSON text
375
- -- Supports: Point, LineString, Polygon, MultiPolygon, MultiPoint, MultiLineString, GeometryCollection
376
- geometry TEXT NOT NULL,
377
-
378
- -- Cache the geometry type for faster queries and validation
379
- geometry_type TEXT NOT NULL CHECK (geometry_type IN (
380
- 'Point', 'LineString', 'Polygon', 'MultiPolygon',
381
- 'MultiPoint', 'MultiLineString', 'GeometryCollection'
382
- )),
383
-
384
- -- Cache bounding box for spatial indexing and quick filtering
385
- bbox_min_lng REAL,
386
- bbox_min_lat REAL,
387
- bbox_max_lng REAL,
388
- bbox_max_lat REAL,
389
-
390
- -- Metadata
391
- created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
392
- updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
393
-
394
- CONSTRAINT fk_geometry_component
395
- FOREIGN KEY (component_id) REFERENCES components(id) ON DELETE CASCADE
396
- );
397
-
398
- -- Indexes for efficient geometry queries
399
- CREATE INDEX idx_component_geometries_component ON component_geometries(component_id);
400
- CREATE INDEX idx_component_geometries_type ON component_geometries(geometry_type);
401
- CREATE INDEX idx_component_geometries_bbox ON component_geometries(
402
- bbox_min_lng, bbox_min_lat, bbox_max_lng, bbox_max_lat
403
- );
404
-
405
- -- Trigger to update timestamp on geometry changes
406
- CREATE TRIGGER update_component_geometries_timestamp
407
- BEFORE UPDATE ON component_geometries
408
- FOR EACH ROW
409
- BEGIN
410
- UPDATE component_geometries
411
- SET updated_at = CURRENT_TIMESTAMP
412
- WHERE id = NEW.id;
413
- END;
414
-
415
326
  -- ============================================================================
416
327
  -- NETWORK CONFIGURATION
417
328
  -- ============================================================================
418
329
 
419
- -- Network configuration parameters - flexible parameter storage
420
- -- Supports scenario-aware configuration with fallback to network defaults
330
+ -- Network configuration parameters with scenario support
421
331
  CREATE TABLE network_config (
422
332
  id INTEGER PRIMARY KEY AUTOINCREMENT,
423
- network_id INTEGER NOT NULL,
424
333
  scenario_id INTEGER, -- NULL for network defaults
425
334
 
426
- -- Parameter definition
427
335
  param_name TEXT NOT NULL,
428
336
  param_type TEXT NOT NULL,
429
337
  param_value TEXT NOT NULL,
430
338
  param_description TEXT,
431
339
 
432
- -- Metadata
433
340
  created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
434
341
  updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
435
342
 
436
- CONSTRAINT fk_network_config_network
437
- FOREIGN KEY (network_id) REFERENCES networks(id) ON DELETE CASCADE,
438
343
  CONSTRAINT fk_network_config_scenario
439
344
  FOREIGN KEY (scenario_id) REFERENCES scenarios(id) ON DELETE CASCADE,
440
345
  CONSTRAINT uq_network_config_param
441
- UNIQUE (network_id, scenario_id, param_name),
346
+ UNIQUE (scenario_id, param_name),
442
347
  CONSTRAINT valid_param_type
443
348
  CHECK (param_type IN ('boolean', 'real', 'integer', 'string', 'json'))
444
349
  );
445
350
 
446
- -- Indexes for performance
447
- CREATE INDEX idx_network_config_network ON network_config(network_id);
448
- CREATE INDEX idx_network_config_scenario ON network_config(scenario_id);
449
- CREATE INDEX idx_network_config_param ON network_config(param_name);
450
- CREATE INDEX idx_network_config_network_param ON network_config(network_id, param_name);
451
- CREATE INDEX idx_network_config_network_scenario_param ON network_config(network_id, scenario_id, param_name);
351
+ CREATE INDEX idx_network_config_lookup ON network_config(scenario_id, param_name);
452
352
 
453
353
  -- ============================================================================
454
354
  -- SYSTEM METADATA
455
355
  -- ============================================================================
456
356
 
457
- -- System metadata table for schema versioning and configuration
357
+ -- System metadata table for schema version tracking and system-level settings
458
358
  CREATE TABLE system_metadata (
459
359
  id INTEGER PRIMARY KEY AUTOINCREMENT,
460
360
  key TEXT NOT NULL UNIQUE,
@@ -466,12 +366,12 @@ CREATE TABLE system_metadata (
466
366
 
467
367
  CREATE INDEX idx_system_metadata_key ON system_metadata(key);
468
368
 
469
- -- Insert initial schema version
369
+ -- Initialize system metadata with schema version
470
370
  INSERT INTO system_metadata (key, value, description)
471
- VALUES ('schema_version', '2.4.0', 'Database schema version - Added network_config table for flexible parameter storage');
371
+ VALUES ('schema_version', '3.1.0', 'Database schema version');
472
372
 
473
- INSERT INTO system_metadata (key, value, description)
474
- VALUES ('created_at', datetime('now'), 'Database creation timestamp');
373
+ -- ============================================================================
374
+ -- SCHEMA VERSION
375
+ -- ============================================================================
475
376
 
476
- INSERT INTO system_metadata (key, value, description)
477
- VALUES ('atomic_operations', 'enabled', 'Atomic database operations support');
377
+ PRAGMA user_version = 31; -- Schema version 3.1