pyconvexity 0.4.8__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.

Files changed (44) hide show
  1. pyconvexity/__init__.py +241 -0
  2. pyconvexity/_version.py +1 -0
  3. pyconvexity/core/__init__.py +60 -0
  4. pyconvexity/core/database.py +485 -0
  5. pyconvexity/core/errors.py +106 -0
  6. pyconvexity/core/types.py +400 -0
  7. pyconvexity/dashboard.py +265 -0
  8. pyconvexity/data/README.md +101 -0
  9. pyconvexity/data/__init__.py +17 -0
  10. pyconvexity/data/loaders/__init__.py +3 -0
  11. pyconvexity/data/loaders/cache.py +213 -0
  12. pyconvexity/data/schema/01_core_schema.sql +420 -0
  13. pyconvexity/data/schema/02_data_metadata.sql +120 -0
  14. pyconvexity/data/schema/03_validation_data.sql +507 -0
  15. pyconvexity/data/sources/__init__.py +5 -0
  16. pyconvexity/data/sources/gem.py +442 -0
  17. pyconvexity/io/__init__.py +26 -0
  18. pyconvexity/io/excel_exporter.py +1226 -0
  19. pyconvexity/io/excel_importer.py +1381 -0
  20. pyconvexity/io/netcdf_exporter.py +191 -0
  21. pyconvexity/io/netcdf_importer.py +1802 -0
  22. pyconvexity/models/__init__.py +195 -0
  23. pyconvexity/models/attributes.py +730 -0
  24. pyconvexity/models/carriers.py +159 -0
  25. pyconvexity/models/components.py +611 -0
  26. pyconvexity/models/network.py +503 -0
  27. pyconvexity/models/results.py +148 -0
  28. pyconvexity/models/scenarios.py +234 -0
  29. pyconvexity/solvers/__init__.py +29 -0
  30. pyconvexity/solvers/pypsa/__init__.py +30 -0
  31. pyconvexity/solvers/pypsa/api.py +446 -0
  32. pyconvexity/solvers/pypsa/batch_loader.py +296 -0
  33. pyconvexity/solvers/pypsa/builder.py +655 -0
  34. pyconvexity/solvers/pypsa/clearing_price.py +678 -0
  35. pyconvexity/solvers/pypsa/constraints.py +405 -0
  36. pyconvexity/solvers/pypsa/solver.py +1442 -0
  37. pyconvexity/solvers/pypsa/storage.py +2096 -0
  38. pyconvexity/timeseries.py +330 -0
  39. pyconvexity/validation/__init__.py +25 -0
  40. pyconvexity/validation/rules.py +312 -0
  41. pyconvexity-0.4.8.dist-info/METADATA +148 -0
  42. pyconvexity-0.4.8.dist-info/RECORD +44 -0
  43. pyconvexity-0.4.8.dist-info/WHEEL +5 -0
  44. pyconvexity-0.4.8.dist-info/top_level.txt +1 -0
@@ -0,0 +1,420 @@
1
+ -- ============================================================================
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
+ -- ============================================================================
7
+
8
+ -- ============================================================================
9
+ -- NETWORK METADATA
10
+ -- ============================================================================
11
+
12
+ -- Network metadata - single row per database file
13
+ CREATE TABLE network_metadata (
14
+ name TEXT NOT NULL,
15
+ description TEXT,
16
+
17
+ -- Time axis definition (single source of truth)
18
+ time_start DATETIME NOT NULL,
19
+ time_end DATETIME NOT NULL,
20
+ time_interval TEXT NOT NULL, -- ISO 8601 duration (PT1H, PT30M, PT2H, etc.)
21
+
22
+ -- Network-level flags
23
+ locked BOOLEAN DEFAULT 0, -- Prevent accidental edits to base network
24
+
25
+ -- Metadata
26
+ created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
27
+ updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
28
+
29
+ CONSTRAINT valid_time_range CHECK (time_end > time_start)
30
+ );
31
+
32
+ -- Network time periods - optimized storage using computed timestamps
33
+ CREATE TABLE network_time_periods (
34
+ period_count INTEGER NOT NULL, -- Total number of periods (e.g., 8760 for hourly year)
35
+ start_timestamp INTEGER NOT NULL, -- Unix timestamp of first period
36
+ interval_seconds INTEGER NOT NULL, -- Seconds between periods (3600 for hourly)
37
+
38
+ CONSTRAINT valid_period_count CHECK (period_count > 0),
39
+ CONSTRAINT valid_interval CHECK (interval_seconds > 0)
40
+ );
41
+
42
+ -- ============================================================================
43
+ -- CARRIERS - ENERGY TYPES
44
+ -- ============================================================================
45
+
46
+ -- Carriers table - energy carriers (electricity, gas, heat, etc.)
47
+ CREATE TABLE carriers (
48
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
49
+ name TEXT NOT NULL UNIQUE,
50
+
51
+ -- Carrier properties from PyPSA reference
52
+ co2_emissions REAL DEFAULT 0.0, -- tonnes/MWh
53
+ color TEXT, -- Plotting color
54
+ nice_name TEXT, -- Display name
55
+ max_growth REAL DEFAULT NULL, -- MW - can be infinite
56
+ max_relative_growth REAL DEFAULT 0.0, -- MW
57
+ curtailable BOOLEAN DEFAULT TRUE, -- Whether the carrier can be curtailed
58
+
59
+ -- Metadata
60
+ created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
61
+ updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
62
+ );
63
+
64
+ -- ============================================================================
65
+ -- UNIFIED COMPONENT SYSTEM
66
+ -- ============================================================================
67
+
68
+ -- Components table - unified table for all network components
69
+ CREATE TABLE components (
70
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
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,
73
+
74
+ -- Geographic location (optional)
75
+ latitude REAL,
76
+ longitude REAL,
77
+ geometry TEXT, -- GeoJSON geometry (Point, LineString, Polygon, etc.)
78
+
79
+ -- Energy carrier reference
80
+ carrier_id INTEGER,
81
+
82
+ -- Bus connections
83
+ bus_id INTEGER, -- Single bus connection
84
+ bus0_id INTEGER, -- First bus for lines/links
85
+ bus1_id INTEGER, -- Second bus for lines/links
86
+
87
+ -- Metadata
88
+ created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
89
+ updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
90
+
91
+ CONSTRAINT fk_components_carrier
92
+ FOREIGN KEY (carrier_id) REFERENCES carriers(id),
93
+ CONSTRAINT fk_components_bus
94
+ FOREIGN KEY (bus_id) REFERENCES components(id),
95
+ CONSTRAINT fk_components_bus0
96
+ FOREIGN KEY (bus0_id) REFERENCES components(id),
97
+ CONSTRAINT fk_components_bus1
98
+ FOREIGN KEY (bus1_id) REFERENCES components(id),
99
+ CONSTRAINT valid_component_type
100
+ CHECK (component_type IN ('BUS', 'GENERATOR', 'LOAD', 'LINE', 'LINK', 'STORAGE_UNIT', 'STORE', 'UNMET_LOAD', 'CONSTRAINT', 'TRANSFORMER', 'SHUNT_IMPEDANCE'))
101
+ );
102
+
103
+ -- Essential indexes only
104
+ CREATE INDEX idx_components_type ON components(component_type);
105
+ CREATE INDEX idx_components_name ON components(name);
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);
109
+
110
+ -- ============================================================================
111
+ -- ATTRIBUTE VALIDATION SYSTEM
112
+ -- ============================================================================
113
+
114
+ -- Attribute validation rules table
115
+ CREATE TABLE attribute_validation_rules (
116
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
117
+ component_type TEXT NOT NULL,
118
+ attribute_name TEXT NOT NULL,
119
+ display_name TEXT,
120
+
121
+ -- Validation rules
122
+ data_type TEXT NOT NULL, -- 'float', 'boolean', 'string', 'int'
123
+ unit TEXT,
124
+ default_value TEXT,
125
+ allowed_storage_types TEXT NOT NULL, -- 'static', 'timeseries', 'static_or_timeseries'
126
+ is_required BOOLEAN DEFAULT FALSE,
127
+ is_input BOOLEAN DEFAULT TRUE,
128
+ description TEXT,
129
+
130
+ -- Constraints
131
+ min_value REAL,
132
+ max_value REAL,
133
+ allowed_values TEXT, -- JSON array
134
+
135
+ -- Grouping
136
+ group_name TEXT DEFAULT 'other',
137
+ to_save BOOLEAN DEFAULT TRUE,
138
+
139
+ CONSTRAINT uq_validation_rule
140
+ UNIQUE (component_type, attribute_name),
141
+ CONSTRAINT valid_component_type_validation
142
+ CHECK (component_type IN ('BUS', 'GENERATOR', 'LOAD', 'LINE', 'LINK', 'STORAGE_UNIT', 'STORE', 'UNMET_LOAD', 'CONSTRAINT', 'TRANSFORMER', 'SHUNT_IMPEDANCE')),
143
+ CONSTRAINT valid_data_type
144
+ CHECK (data_type IN ('float', 'boolean', 'string', 'int')),
145
+ CONSTRAINT valid_allowed_storage_types
146
+ CHECK (allowed_storage_types IN ('static', 'timeseries', 'static_or_timeseries')),
147
+ CONSTRAINT valid_group_name
148
+ CHECK (group_name IN ('basic', 'capacity', 'power_limits', 'energy', 'unit_commitment', 'ramping', 'costs', 'electrical'))
149
+ );
150
+
151
+ -- Essential indexes only
152
+ CREATE INDEX idx_validation_component_type ON attribute_validation_rules(component_type);
153
+ CREATE INDEX idx_validation_lookup ON attribute_validation_rules(component_type, attribute_name);
154
+
155
+ -- ============================================================================
156
+ -- SCENARIOS - SPARSE OVERRIDE APPROACH
157
+ -- ============================================================================
158
+
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
+ -- System scenarios (is_system_scenario = TRUE) are reserved for special purposes like "Actual" values
163
+ CREATE TABLE scenarios (
164
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
165
+ name TEXT NOT NULL UNIQUE,
166
+ description TEXT,
167
+ probability REAL DEFAULT NULL, -- For stochastic optimization (NULL = deterministic what-if)
168
+
169
+ -- System scenario flags
170
+ is_system_scenario BOOLEAN DEFAULT FALSE, -- TRUE = system-reserved, cannot delete, excluded from solves
171
+ system_purpose TEXT DEFAULT NULL, -- 'actual' for actual/measured values, NULL for user scenarios
172
+
173
+ created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
174
+
175
+ CONSTRAINT valid_probability
176
+ CHECK (probability IS NULL OR (probability >= 0 AND probability <= 1)),
177
+ CONSTRAINT valid_system_purpose
178
+ CHECK (system_purpose IS NULL OR system_purpose IN ('actual'))
179
+ );
180
+
181
+ -- ============================================================================
182
+ -- SYSTEM SCENARIO MANAGEMENT
183
+ -- ============================================================================
184
+
185
+ -- Trigger to auto-create the "Actual" system scenario when network_metadata is created
186
+ CREATE TRIGGER create_actual_scenario_on_network_create
187
+ AFTER INSERT ON network_metadata
188
+ FOR EACH ROW
189
+ WHEN NOT EXISTS (SELECT 1 FROM scenarios WHERE system_purpose = 'actual')
190
+ BEGIN
191
+ INSERT INTO scenarios (name, description, is_system_scenario, system_purpose)
192
+ VALUES ('Actual', 'Actual/measured values for validation and comparison', TRUE, 'actual');
193
+ END;
194
+
195
+ -- Trigger to prevent deletion of system scenarios
196
+ CREATE TRIGGER prevent_system_scenario_deletion
197
+ BEFORE DELETE ON scenarios
198
+ FOR EACH ROW
199
+ WHEN OLD.is_system_scenario = TRUE
200
+ BEGIN
201
+ SELECT RAISE(ABORT, 'Cannot delete system scenarios');
202
+ END;
203
+
204
+ -- Trigger to prevent modification of system scenario flags
205
+ CREATE TRIGGER prevent_system_scenario_modification
206
+ BEFORE UPDATE ON scenarios
207
+ FOR EACH ROW
208
+ WHEN OLD.is_system_scenario = TRUE AND (
209
+ NEW.is_system_scenario != OLD.is_system_scenario OR
210
+ NEW.system_purpose != OLD.system_purpose
211
+ )
212
+ BEGIN
213
+ SELECT RAISE(ABORT, 'Cannot modify system scenario flags');
214
+ END;
215
+
216
+ -- ============================================================================
217
+ -- UNIFIED COMPONENT ATTRIBUTES - SPARSE SCENARIOS + RAW TIMESERIES
218
+ -- ============================================================================
219
+
220
+ -- Component attributes - sparse scenario overrides
221
+ -- scenario_id = NULL → Base network (editable)
222
+ -- scenario_id = 1 → Scenario 1 (overrides base, read-only)
223
+ CREATE TABLE component_attributes (
224
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
225
+ component_id INTEGER NOT NULL,
226
+ attribute_name TEXT NOT NULL,
227
+
228
+ -- Scenario support - NULL = base network, non-NULL = scenario override
229
+ scenario_id INTEGER, -- NULLABLE!
230
+
231
+ -- Storage type
232
+ storage_type TEXT NOT NULL CHECK (storage_type IN ('static', 'timeseries')),
233
+
234
+ -- Value storage
235
+ static_value TEXT, -- JSON-encoded static value (all data types)
236
+ timeseries_data BLOB, -- Raw f32 array (NOT Parquet!)
237
+
238
+ -- Cached metadata
239
+ data_type TEXT NOT NULL,
240
+ unit TEXT,
241
+ is_input BOOLEAN DEFAULT TRUE,
242
+
243
+ -- Metadata
244
+ created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
245
+ updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
246
+
247
+ CONSTRAINT fk_attributes_component
248
+ FOREIGN KEY (component_id) REFERENCES components(id) ON DELETE CASCADE,
249
+ CONSTRAINT fk_attributes_scenario
250
+ FOREIGN KEY (scenario_id) REFERENCES scenarios(id) ON DELETE CASCADE,
251
+
252
+ -- Storage validation
253
+ CONSTRAINT check_exactly_one_storage_type CHECK (
254
+ (storage_type = 'static' AND static_value IS NOT NULL AND timeseries_data IS NULL) OR
255
+ (storage_type = 'timeseries' AND static_value IS NULL AND timeseries_data IS NOT NULL)
256
+ ),
257
+
258
+ -- Unique per component/attribute/scenario (NULL scenario counts as unique value)
259
+ CONSTRAINT uq_component_attribute_scenario
260
+ UNIQUE (component_id, attribute_name, scenario_id)
261
+ );
262
+
263
+ -- Essential indexes only
264
+ CREATE INDEX idx_attributes_lookup ON component_attributes(
265
+ component_id, attribute_name, scenario_id
266
+ );
267
+ CREATE INDEX idx_attributes_scenario ON component_attributes(scenario_id);
268
+
269
+ -- ============================================================================
270
+ -- SCENARIO CACHE - MATERIALIZED VIEW FOR FAST SCENARIO COUNTING
271
+ -- ============================================================================
272
+
273
+ -- Scenario cache - stores precomputed scenario counts and details
274
+ -- This is a materialized view that's kept in sync via application code
275
+ -- Updated transactionally whenever attribute values change
276
+ CREATE TABLE IF NOT EXISTS attribute_scenario_cache (
277
+ component_id INTEGER NOT NULL,
278
+ attribute_name TEXT NOT NULL,
279
+
280
+ -- Cached computed values
281
+ scenario_count INTEGER NOT NULL DEFAULT 1, -- Display count (includes synthetic base)
282
+ has_base_value BOOLEAN NOT NULL DEFAULT FALSE, -- TRUE if base network has value
283
+ has_scenario_values BOOLEAN NOT NULL DEFAULT FALSE, -- TRUE if any scenarios have values
284
+
285
+ -- Scenario details for dropdown (JSON array)
286
+ -- Format: [{scenario_id: 0, scenario_name: "Base", value: "123", has_value: true}, ...]
287
+ scenario_details TEXT,
288
+
289
+ -- Metadata
290
+ last_updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
291
+
292
+ PRIMARY KEY (component_id, attribute_name),
293
+ CONSTRAINT fk_scenario_cache_component
294
+ FOREIGN KEY (component_id) REFERENCES components(id) ON DELETE CASCADE
295
+ );
296
+
297
+ -- Index for fast component-based lookups
298
+ CREATE INDEX IF NOT EXISTS idx_scenario_cache_component
299
+ ON attribute_scenario_cache(component_id);
300
+
301
+ -- Index for bulk table loads
302
+ CREATE INDEX IF NOT EXISTS idx_scenario_cache_component_type
303
+ ON attribute_scenario_cache(component_id, attribute_name);
304
+
305
+ -- NOTES:
306
+ -- This cache is maintained by application code (Rust) in the same transaction
307
+ -- as attribute writes. This ensures ACID guarantees - the cache can never be
308
+ -- stale or inconsistent with the actual data.
309
+ --
310
+ -- Cache logic:
311
+ -- - If has_scenario_values: scenario_count = num_scenarios + 1 (always include base)
312
+ -- - If only has_base_value: scenario_count = 1 (no indicator shown)
313
+ -- - If neither: scenario_count = 0 (no values at all)
314
+
315
+ -- ============================================================================
316
+ -- VALIDATION TRIGGERS
317
+ -- ============================================================================
318
+
319
+ -- Trigger to validate attributes against rules on insert
320
+ CREATE TRIGGER validate_component_attribute_insert
321
+ BEFORE INSERT ON component_attributes
322
+ FOR EACH ROW
323
+ WHEN NOT EXISTS (
324
+ SELECT 1 FROM components c
325
+ JOIN attribute_validation_rules avr ON c.component_type = avr.component_type
326
+ WHERE c.id = NEW.component_id
327
+ AND avr.attribute_name = NEW.attribute_name
328
+ )
329
+ BEGIN
330
+ SELECT RAISE(ABORT, 'Attribute is not defined for this component type');
331
+ END;
332
+
333
+ -- Trigger to validate storage type on insert
334
+ CREATE TRIGGER validate_storage_type_insert
335
+ BEFORE INSERT ON component_attributes
336
+ FOR EACH ROW
337
+ WHEN EXISTS (
338
+ SELECT 1 FROM components c
339
+ JOIN attribute_validation_rules avr ON c.component_type = avr.component_type
340
+ WHERE c.id = NEW.component_id
341
+ AND avr.attribute_name = NEW.attribute_name
342
+ AND avr.allowed_storage_types != 'static_or_timeseries'
343
+ AND avr.allowed_storage_types != NEW.storage_type
344
+ )
345
+ BEGIN
346
+ SELECT RAISE(ABORT, 'Storage type not allowed for this attribute');
347
+ END;
348
+
349
+ -- Trigger to update timestamps
350
+ CREATE TRIGGER update_component_attributes_timestamp
351
+ BEFORE UPDATE ON component_attributes
352
+ FOR EACH ROW
353
+ BEGIN
354
+ UPDATE component_attributes
355
+ SET updated_at = CURRENT_TIMESTAMP
356
+ WHERE id = NEW.id;
357
+ END;
358
+
359
+ -- Trigger to update component timestamps when attributes change
360
+ CREATE TRIGGER update_component_timestamp_on_attribute_change
361
+ AFTER INSERT ON component_attributes
362
+ FOR EACH ROW
363
+ BEGIN
364
+ UPDATE components
365
+ SET updated_at = CURRENT_TIMESTAMP
366
+ WHERE id = NEW.component_id;
367
+ END;
368
+
369
+ -- ============================================================================
370
+ -- NETWORK CONFIGURATION
371
+ -- ============================================================================
372
+
373
+ -- Network configuration parameters with scenario support
374
+ CREATE TABLE network_config (
375
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
376
+ scenario_id INTEGER, -- NULL for network defaults
377
+
378
+ param_name TEXT NOT NULL,
379
+ param_type TEXT NOT NULL,
380
+ param_value TEXT NOT NULL,
381
+ param_description TEXT,
382
+
383
+ created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
384
+ updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
385
+
386
+ CONSTRAINT fk_network_config_scenario
387
+ FOREIGN KEY (scenario_id) REFERENCES scenarios(id) ON DELETE CASCADE,
388
+ CONSTRAINT uq_network_config_param
389
+ UNIQUE (scenario_id, param_name),
390
+ CONSTRAINT valid_param_type
391
+ CHECK (param_type IN ('boolean', 'real', 'integer', 'string', 'json'))
392
+ );
393
+
394
+ CREATE INDEX idx_network_config_lookup ON network_config(scenario_id, param_name);
395
+
396
+ -- ============================================================================
397
+ -- SYSTEM METADATA
398
+ -- ============================================================================
399
+
400
+ -- System metadata table for schema version tracking and system-level settings
401
+ CREATE TABLE system_metadata (
402
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
403
+ key TEXT NOT NULL UNIQUE,
404
+ value TEXT NOT NULL,
405
+ description TEXT,
406
+ created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
407
+ updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
408
+ );
409
+
410
+ CREATE INDEX idx_system_metadata_key ON system_metadata(key);
411
+
412
+ -- Initialize system metadata with schema version
413
+ INSERT INTO system_metadata (key, value, description)
414
+ VALUES ('schema_version', '3.1.0', 'Database schema version');
415
+
416
+ -- ============================================================================
417
+ -- SCHEMA VERSION
418
+ -- ============================================================================
419
+
420
+ PRAGMA user_version = 31; -- Schema version 3.1
@@ -0,0 +1,120 @@
1
+ -- ============================================================================
2
+ -- DATA STORAGE AND METADATA SCHEMA
3
+ -- Essential tables for data storage and solve results
4
+ -- Version 3.1.0 - Simplified for single-network-per-file
5
+ -- ============================================================================
6
+
7
+ -- ============================================================================
8
+ -- GENERIC DATA STORAGE
9
+ -- ============================================================================
10
+
11
+ -- Generic data store for arbitrary network-level data
12
+ CREATE TABLE network_data_store (
13
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
14
+ category TEXT NOT NULL, -- 'config', 'results', 'statistics', 'scripts', etc.
15
+ name TEXT NOT NULL,
16
+ data_format TEXT DEFAULT 'json', -- 'json', 'csv', 'binary', 'text'
17
+ data BLOB NOT NULL,
18
+ metadata TEXT, -- JSON metadata
19
+ checksum TEXT,
20
+
21
+ created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
22
+ updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
23
+
24
+ CONSTRAINT uq_datastore_category_name
25
+ UNIQUE (category, name),
26
+ CONSTRAINT valid_data_format
27
+ CHECK (data_format IN ('json', 'csv', 'binary', 'text', 'yaml', 'toml'))
28
+ );
29
+
30
+ CREATE INDEX idx_datastore_category ON network_data_store(category);
31
+
32
+ -- ============================================================================
33
+ -- DOCUMENTATION AND NOTES
34
+ -- ============================================================================
35
+
36
+ -- Network-level notes
37
+ CREATE TABLE network_notes (
38
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
39
+ title TEXT NOT NULL,
40
+ content TEXT,
41
+ tags TEXT, -- JSON array
42
+ note_type TEXT DEFAULT 'note',
43
+ priority INTEGER DEFAULT 0,
44
+
45
+ created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
46
+ updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
47
+
48
+ CONSTRAINT valid_note_type
49
+ CHECK (note_type IN ('note', 'todo', 'warning', 'info', 'doc'))
50
+ );
51
+
52
+ -- Component-specific notes
53
+ CREATE TABLE component_notes (
54
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
55
+ component_id INTEGER NOT NULL,
56
+ title TEXT NOT NULL,
57
+ content TEXT,
58
+ tags TEXT,
59
+ note_type TEXT DEFAULT 'note',
60
+ priority INTEGER DEFAULT 0,
61
+
62
+ created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
63
+ updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
64
+
65
+ CONSTRAINT fk_component_notes_component
66
+ FOREIGN KEY (component_id) REFERENCES components(id) ON DELETE CASCADE,
67
+ CONSTRAINT valid_component_note_type
68
+ CHECK (note_type IN ('note', 'todo', 'warning', 'info', 'doc'))
69
+ );
70
+
71
+ CREATE INDEX idx_component_notes_component ON component_notes(component_id);
72
+
73
+ -- ============================================================================
74
+ -- SOLVE RESULTS AND STATISTICS
75
+ -- ============================================================================
76
+
77
+ -- Network solve results - stores solver outputs per scenario
78
+ CREATE TABLE network_solve_results (
79
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
80
+ scenario_id INTEGER, -- NULL for base network, non-NULL for scenario
81
+
82
+ -- Solve metadata
83
+ solved_at DATETIME DEFAULT CURRENT_TIMESTAMP,
84
+ solver_name TEXT NOT NULL,
85
+ solve_type TEXT NOT NULL,
86
+ solve_status TEXT NOT NULL,
87
+ objective_value REAL,
88
+ solve_time_seconds REAL,
89
+
90
+ -- Results stored as JSON
91
+ results_json TEXT NOT NULL,
92
+ metadata_json TEXT,
93
+
94
+ -- Only one result per scenario (including NULL for base network)
95
+ UNIQUE (scenario_id),
96
+
97
+ FOREIGN KEY (scenario_id) REFERENCES scenarios(id) ON DELETE CASCADE
98
+ );
99
+
100
+ CREATE INDEX idx_solve_results_scenario ON network_solve_results(scenario_id);
101
+
102
+ -- Year-based solve results for capacity expansion analysis
103
+ CREATE TABLE network_solve_results_by_year (
104
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
105
+ scenario_id INTEGER, -- NULL for base network
106
+ year INTEGER NOT NULL,
107
+
108
+ results_json TEXT NOT NULL,
109
+ metadata_json TEXT,
110
+
111
+ created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
112
+
113
+ CONSTRAINT fk_solve_results_year_scenario
114
+ FOREIGN KEY (scenario_id) REFERENCES scenarios(id) ON DELETE CASCADE,
115
+ CONSTRAINT uq_solve_results_year_unique
116
+ UNIQUE (scenario_id, year),
117
+ CONSTRAINT valid_year CHECK (year >= 1900 AND year <= 2100)
118
+ );
119
+
120
+ CREATE INDEX idx_solve_results_year_scenario ON network_solve_results_by_year(scenario_id);