pyconvexity 0.1.2__py3-none-any.whl → 0.1.4__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 (43) hide show
  1. pyconvexity/__init__.py +57 -8
  2. pyconvexity/_version.py +1 -2
  3. pyconvexity/core/__init__.py +0 -2
  4. pyconvexity/core/database.py +158 -0
  5. pyconvexity/core/types.py +105 -18
  6. pyconvexity/data/README.md +101 -0
  7. pyconvexity/data/__init__.py +18 -0
  8. pyconvexity/data/__pycache__/__init__.cpython-313.pyc +0 -0
  9. pyconvexity/data/loaders/__init__.py +3 -0
  10. pyconvexity/data/loaders/__pycache__/__init__.cpython-313.pyc +0 -0
  11. pyconvexity/data/loaders/__pycache__/cache.cpython-313.pyc +0 -0
  12. pyconvexity/data/loaders/cache.py +212 -0
  13. pyconvexity/data/schema/01_core_schema.sql +12 -12
  14. pyconvexity/data/schema/02_data_metadata.sql +17 -321
  15. pyconvexity/data/sources/__init__.py +5 -0
  16. pyconvexity/data/sources/__pycache__/__init__.cpython-313.pyc +0 -0
  17. pyconvexity/data/sources/__pycache__/gem.cpython-313.pyc +0 -0
  18. pyconvexity/data/sources/gem.py +412 -0
  19. pyconvexity/io/__init__.py +32 -0
  20. pyconvexity/io/excel_exporter.py +1012 -0
  21. pyconvexity/io/excel_importer.py +1109 -0
  22. pyconvexity/io/netcdf_exporter.py +192 -0
  23. pyconvexity/io/netcdf_importer.py +1602 -0
  24. pyconvexity/models/__init__.py +7 -0
  25. pyconvexity/models/attributes.py +209 -72
  26. pyconvexity/models/components.py +3 -0
  27. pyconvexity/models/network.py +17 -15
  28. pyconvexity/models/scenarios.py +177 -0
  29. pyconvexity/solvers/__init__.py +29 -0
  30. pyconvexity/solvers/pypsa/__init__.py +24 -0
  31. pyconvexity/solvers/pypsa/api.py +421 -0
  32. pyconvexity/solvers/pypsa/batch_loader.py +304 -0
  33. pyconvexity/solvers/pypsa/builder.py +566 -0
  34. pyconvexity/solvers/pypsa/constraints.py +321 -0
  35. pyconvexity/solvers/pypsa/solver.py +1106 -0
  36. pyconvexity/solvers/pypsa/storage.py +1574 -0
  37. pyconvexity/timeseries.py +327 -0
  38. pyconvexity/validation/rules.py +2 -2
  39. {pyconvexity-0.1.2.dist-info → pyconvexity-0.1.4.dist-info}/METADATA +5 -2
  40. pyconvexity-0.1.4.dist-info/RECORD +46 -0
  41. pyconvexity-0.1.2.dist-info/RECORD +0 -20
  42. {pyconvexity-0.1.2.dist-info → pyconvexity-0.1.4.dist-info}/WHEEL +0 -0
  43. {pyconvexity-0.1.2.dist-info → pyconvexity-0.1.4.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,212 @@
1
+ """
2
+ Caching functionality for PyConvexity data operations.
3
+
4
+ This module handles caching of processed datasets to improve performance.
5
+ """
6
+
7
+ import pandas as pd
8
+ import hashlib
9
+ import json
10
+ from pathlib import Path
11
+ from typing import Dict, Any, Optional
12
+ import logging
13
+ from datetime import datetime, timedelta
14
+
15
+ logger = logging.getLogger(__name__)
16
+
17
+ class DataCache:
18
+ """Manages caching of processed datasets."""
19
+
20
+ def __init__(self, cache_dir: Optional[str] = None):
21
+ """
22
+ Initialize the cache manager.
23
+
24
+ Args:
25
+ cache_dir: Directory to store cache files. Defaults to 'data/cache'
26
+ """
27
+ if cache_dir is None:
28
+ cache_dir = "data/cache"
29
+
30
+ self.cache_dir = Path(cache_dir)
31
+ self.cache_dir.mkdir(parents=True, exist_ok=True)
32
+
33
+ # Cache metadata file
34
+ self.metadata_file = self.cache_dir / "cache_metadata.json"
35
+ self._load_metadata()
36
+
37
+ def _load_metadata(self):
38
+ """Load cache metadata from file."""
39
+ if self.metadata_file.exists():
40
+ try:
41
+ with open(self.metadata_file, 'r') as f:
42
+ self.metadata = json.load(f)
43
+ except (json.JSONDecodeError, FileNotFoundError):
44
+ self.metadata = {}
45
+ else:
46
+ self.metadata = {}
47
+
48
+ def _save_metadata(self):
49
+ """Save cache metadata to file."""
50
+ with open(self.metadata_file, 'w') as f:
51
+ json.dump(self.metadata, f, indent=2)
52
+
53
+ def _get_cache_key(self, dataset_name: str, filters: Dict[str, Any]) -> str:
54
+ """Generate a unique cache key for a dataset and filters combination."""
55
+ # Create a hash of the filters
56
+ filters_str = json.dumps(filters, sort_keys=True)
57
+ filters_hash = hashlib.md5(filters_str.encode()).hexdigest()
58
+
59
+ return f"{dataset_name}_{filters_hash}"
60
+
61
+ def _get_cache_file_path(self, cache_key: str) -> Path:
62
+ """Get the file path for a cache key."""
63
+ return self.cache_dir / f"{cache_key}.parquet"
64
+
65
+ def get_cached_data(
66
+ self,
67
+ dataset_name: str,
68
+ filters: Dict[str, Any]
69
+ ) -> Optional[pd.DataFrame]:
70
+ """
71
+ Retrieve cached data if available and not expired.
72
+
73
+ Args:
74
+ dataset_name: Name of the dataset
75
+ filters: Filters applied to the dataset
76
+
77
+ Returns:
78
+ pandas.DataFrame or None: Cached data if available and valid
79
+ """
80
+ cache_key = self._get_cache_key(dataset_name, filters)
81
+ cache_file = self._get_cache_file_path(cache_key)
82
+
83
+ # Check if cache file exists
84
+ if not cache_file.exists():
85
+ return None
86
+
87
+ # Check if cache entry exists in metadata
88
+ if cache_key not in self.metadata:
89
+ # Clean up orphaned cache file
90
+ cache_file.unlink(missing_ok=True)
91
+ return None
92
+
93
+ # Check if cache is expired (default: 7 days)
94
+ cache_info = self.metadata[cache_key]
95
+ created_time = datetime.fromisoformat(cache_info['created'])
96
+ max_age = timedelta(days=cache_info.get('max_age_days', 7))
97
+
98
+ if datetime.now() - created_time > max_age:
99
+ logger.info(f"Cache expired for '{dataset_name}', removing...")
100
+ self._remove_cache_entry(cache_key)
101
+ return None
102
+
103
+ # Load cached data
104
+ try:
105
+ cached_data = pd.read_parquet(cache_file)
106
+ logger.info(f"Loaded cached data for '{dataset_name}' ({len(cached_data)} rows)")
107
+ return cached_data
108
+ except Exception as e:
109
+ logger.warning(f"Failed to load cached data for '{dataset_name}': {e}")
110
+ self._remove_cache_entry(cache_key)
111
+ return None
112
+
113
+ def cache_data(
114
+ self,
115
+ dataset_name: str,
116
+ data: pd.DataFrame,
117
+ filters: Dict[str, Any],
118
+ max_age_days: int = 7
119
+ ):
120
+ """
121
+ Cache processed data.
122
+
123
+ Args:
124
+ dataset_name: Name of the dataset
125
+ data: Processed pandas DataFrame
126
+ filters: Filters applied to the dataset
127
+ max_age_days: Maximum age of cache in days
128
+ """
129
+ cache_key = self._get_cache_key(dataset_name, filters)
130
+ cache_file = self._get_cache_file_path(cache_key)
131
+
132
+ # Save data to parquet file
133
+ data.to_parquet(cache_file, index=False)
134
+
135
+ # Update metadata
136
+ self.metadata[cache_key] = {
137
+ 'dataset_name': dataset_name,
138
+ 'filters': filters,
139
+ 'created': datetime.now().isoformat(),
140
+ 'max_age_days': max_age_days,
141
+ 'rows': len(data),
142
+ 'columns': list(data.columns)
143
+ }
144
+
145
+ self._save_metadata()
146
+ logger.info(f"Cached data for '{dataset_name}' ({len(data)} rows)")
147
+
148
+ def _remove_cache_entry(self, cache_key: str):
149
+ """Remove a cache entry and its file."""
150
+ cache_file = self._get_cache_file_path(cache_key)
151
+ cache_file.unlink(missing_ok=True)
152
+
153
+ if cache_key in self.metadata:
154
+ del self.metadata[cache_key]
155
+ self._save_metadata()
156
+
157
+ def clear_cache(self, dataset_name: Optional[str] = None):
158
+ """
159
+ Clear cache entries.
160
+
161
+ Args:
162
+ dataset_name: If provided, only clear cache for this dataset
163
+ """
164
+ keys_to_remove = []
165
+
166
+ for cache_key, info in self.metadata.items():
167
+ if dataset_name is None or info['dataset_name'] == dataset_name:
168
+ keys_to_remove.append(cache_key)
169
+
170
+ for key in keys_to_remove:
171
+ self._remove_cache_entry(key)
172
+
173
+ logger.info(f"Cleared {len(keys_to_remove)} cache entries")
174
+
175
+ def get_cache_info(self) -> Dict[str, Any]:
176
+ """Get information about the cache."""
177
+ total_size = 0
178
+ dataset_counts = {}
179
+
180
+ for cache_key, info in self.metadata.items():
181
+ dataset_name = info['dataset_name']
182
+ dataset_counts[dataset_name] = dataset_counts.get(dataset_name, 0) + 1
183
+
184
+ cache_file = self._get_cache_file_path(cache_key)
185
+ if cache_file.exists():
186
+ total_size += cache_file.stat().st_size
187
+
188
+ return {
189
+ 'total_entries': len(self.metadata),
190
+ 'total_size_mb': round(total_size / (1024 * 1024), 2),
191
+ 'dataset_counts': dataset_counts,
192
+ 'cache_dir': str(self.cache_dir)
193
+ }
194
+
195
+ def cleanup_expired_cache(self):
196
+ """Remove expired cache entries."""
197
+ expired_keys = []
198
+
199
+ for cache_key, info in self.metadata.items():
200
+ created_time = datetime.fromisoformat(info['created'])
201
+ max_age = timedelta(days=info.get('max_age_days', 7))
202
+
203
+ if datetime.now() - created_time > max_age:
204
+ expired_keys.append(cache_key)
205
+
206
+ for key in expired_keys:
207
+ self._remove_cache_entry(key)
208
+
209
+ if expired_keys:
210
+ logger.info(f"Cleaned up {len(expired_keys)} expired cache entries")
211
+ else:
212
+ logger.info("No expired cache entries found")
@@ -34,25 +34,25 @@ CREATE TABLE networks (
34
34
  CREATE INDEX idx_networks_name ON networks(name);
35
35
  CREATE INDEX idx_networks_created_at ON networks(created_at);
36
36
 
37
- -- Network time periods - computed from time axis definition
38
- -- This table is populated automatically based on network time axis
37
+ -- 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
39
40
  CREATE TABLE network_time_periods (
40
- id INTEGER PRIMARY KEY AUTOINCREMENT,
41
41
  network_id INTEGER NOT NULL,
42
- timestamp DATETIME NOT NULL,
43
- period_index INTEGER NOT NULL, -- 0-based index for array operations
42
+ period_count INTEGER NOT NULL, -- Total number of periods (e.g., 8760 for hourly year)
43
+ start_timestamp INTEGER NOT NULL, -- Unix timestamp of first period
44
+ interval_seconds INTEGER NOT NULL, -- Seconds between periods (3600 for hourly)
45
+
46
+ PRIMARY KEY (network_id),
44
47
 
45
48
  CONSTRAINT fk_time_periods_network
46
49
  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)
50
+ CONSTRAINT valid_period_count CHECK (period_count > 0),
51
+ CONSTRAINT valid_interval CHECK (interval_seconds > 0)
51
52
  );
52
53
 
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);
54
+ -- No additional indexes needed - primary key on network_id is sufficient
55
+ -- Timestamps are computed as: start_timestamp + (period_index * interval_seconds)
56
56
 
57
57
  -- Network locks - prevents concurrent modifications
58
58
  CREATE TABLE network_locks (
@@ -1,8 +1,8 @@
1
1
  -- ============================================================================
2
- -- DATA STORAGE AND METADATA SCHEMA
3
- -- Auxiliary tables for data storage, notes, audit logging, and analysis caching
4
- -- Optimized for atomic operations and data management
5
- -- Version 2.1.0
2
+ -- DATA STORAGE AND METADATA SCHEMA (OPTIMIZED)
3
+ -- Essential tables for data storage and solve results only
4
+ -- Removed unused audit logging and analysis caching for efficiency
5
+ -- Version 2.2.0 - Optimized
6
6
  -- ============================================================================
7
7
 
8
8
  -- ============================================================================
@@ -34,12 +34,9 @@ CREATE TABLE network_data_store (
34
34
  CHECK (data_format IN ('json', 'parquet', 'csv', 'binary', 'text', 'yaml', 'toml'))
35
35
  );
36
36
 
37
- -- Optimized indexes for data retrieval
37
+ -- Minimal indexes for data retrieval
38
38
  CREATE INDEX idx_datastore_network ON network_data_store(network_id);
39
39
  CREATE INDEX idx_datastore_category ON network_data_store(network_id, category);
40
- CREATE INDEX idx_datastore_name ON network_data_store(network_id, category, name);
41
- CREATE INDEX idx_datastore_created_at ON network_data_store(created_at);
42
- CREATE INDEX idx_datastore_format ON network_data_store(data_format);
43
40
 
44
41
  -- ============================================================================
45
42
  -- DOCUMENTATION AND NOTES
@@ -66,11 +63,8 @@ CREATE TABLE network_notes (
66
63
  CHECK (note_type IN ('note', 'todo', 'warning', 'info', 'doc'))
67
64
  );
68
65
 
66
+ -- Minimal indexes for notes
69
67
  CREATE INDEX idx_notes_network ON network_notes(network_id);
70
- CREATE INDEX idx_notes_title ON network_notes(network_id, title);
71
- CREATE INDEX idx_notes_type ON network_notes(note_type);
72
- CREATE INDEX idx_notes_priority ON network_notes(priority);
73
- CREATE INDEX idx_notes_created_at ON network_notes(created_at);
74
68
 
75
69
  -- Component-specific notes and documentation
76
70
  CREATE TABLE component_notes (
@@ -93,139 +87,13 @@ CREATE TABLE component_notes (
93
87
  CHECK (note_type IN ('note', 'todo', 'warning', 'info', 'doc'))
94
88
  );
95
89
 
90
+ -- Minimal indexes for component notes
96
91
  CREATE INDEX idx_component_notes_component ON component_notes(component_id);
97
- CREATE INDEX idx_component_notes_title ON component_notes(component_id, title);
98
- CREATE INDEX idx_component_notes_type ON component_notes(note_type);
99
- CREATE INDEX idx_component_notes_priority ON component_notes(priority);
100
- CREATE INDEX idx_component_notes_created_at ON component_notes(created_at);
101
-
102
- -- ============================================================================
103
- -- AUDIT AND CHANGE TRACKING
104
- -- ============================================================================
105
-
106
- -- Comprehensive audit log for tracking all database changes
107
- CREATE TABLE audit_log (
108
- id INTEGER PRIMARY KEY AUTOINCREMENT,
109
- network_id INTEGER,
110
- table_name TEXT NOT NULL,
111
- record_id INTEGER,
112
- operation TEXT NOT NULL CHECK (operation IN ('INSERT', 'UPDATE', 'DELETE')),
113
-
114
- -- Change data
115
- old_values TEXT, -- JSON of old values for UPDATE/DELETE
116
- new_values TEXT, -- JSON of new values for INSERT/UPDATE
117
- change_summary TEXT, -- Human-readable summary of changes
118
- affected_fields TEXT, -- JSON array of changed field names
119
-
120
- -- Context and metadata
121
- user_id TEXT,
122
- session_id TEXT,
123
- client_info TEXT, -- Application version, client type, etc.
124
- transaction_id TEXT, -- For grouping related changes
125
- timestamp DATETIME DEFAULT CURRENT_TIMESTAMP,
126
-
127
- -- Performance metrics
128
- execution_time_ms INTEGER, -- Time taken for the operation
129
-
130
- CONSTRAINT fk_audit_network
131
- FOREIGN KEY (network_id) REFERENCES networks(id) ON DELETE CASCADE
132
- );
133
-
134
- -- Optimized indexes for audit queries
135
- CREATE INDEX idx_audit_network ON audit_log(network_id);
136
- CREATE INDEX idx_audit_table ON audit_log(table_name);
137
- CREATE INDEX idx_audit_timestamp ON audit_log(timestamp);
138
- CREATE INDEX idx_audit_operation ON audit_log(operation);
139
- CREATE INDEX idx_audit_record ON audit_log(table_name, record_id);
140
- CREATE INDEX idx_audit_user ON audit_log(user_id);
141
- CREATE INDEX idx_audit_session ON audit_log(session_id);
142
- CREATE INDEX idx_audit_transaction ON audit_log(transaction_id);
143
-
144
- -- Change summary view for recent activity
145
- CREATE VIEW recent_changes AS
146
- SELECT
147
- al.id,
148
- al.network_id,
149
- n.name as network_name,
150
- al.table_name,
151
- al.record_id,
152
- al.operation,
153
- al.change_summary,
154
- al.user_id,
155
- al.timestamp,
156
- al.execution_time_ms
157
- FROM audit_log al
158
- LEFT JOIN networks n ON al.network_id = n.id
159
- ORDER BY al.timestamp DESC
160
- LIMIT 1000;
161
-
162
- -- ============================================================================
163
- -- NETWORK ANALYSIS AND RESULTS CACHING
164
- -- ============================================================================
165
-
166
- -- Cache for storing analysis results and computed data
167
- -- This improves performance by avoiding recomputation of expensive operations
168
- CREATE TABLE network_analysis_cache (
169
- id INTEGER PRIMARY KEY AUTOINCREMENT,
170
- network_id INTEGER NOT NULL,
171
- analysis_type TEXT NOT NULL, -- 'optimization', 'statistics', 'validation', 'powerflow', etc.
172
- analysis_key TEXT NOT NULL, -- Unique key for this analysis (hash of inputs)
173
- analysis_version TEXT, -- Version of analysis algorithm
174
-
175
- -- Input tracking
176
- input_hash TEXT, -- Hash of inputs that generated this result
177
- input_summary TEXT, -- Human-readable summary of inputs
178
- dependencies TEXT, -- JSON array of dependent data (components, attributes, etc.)
179
-
180
- -- Results storage
181
- result_data BLOB NOT NULL, -- Serialized results (typically JSON or Parquet)
182
- result_format TEXT DEFAULT 'json', -- 'json', 'parquet', 'csv', 'binary'
183
- result_summary TEXT, -- Human-readable summary of results
184
- result_size_bytes INTEGER, -- Size of result data for management
185
-
186
- -- Analysis metadata
187
- analysis_time_ms INTEGER, -- Time taken for analysis
188
- status TEXT DEFAULT 'completed', -- 'completed', 'failed', 'in_progress', 'stale'
189
- error_message TEXT, -- If status is 'failed'
190
- warnings TEXT, -- JSON array of warnings
191
-
192
- -- Cache management
193
- hit_count INTEGER DEFAULT 0, -- Number of times this cache entry was used
194
- last_accessed DATETIME DEFAULT CURRENT_TIMESTAMP,
195
- expires_at DATETIME, -- Optional expiration for cache cleanup
196
-
197
- -- Metadata
198
- created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
199
- updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
200
- created_by TEXT,
201
-
202
- CONSTRAINT fk_analysis_cache_network
203
- FOREIGN KEY (network_id) REFERENCES networks(id) ON DELETE CASCADE,
204
- CONSTRAINT uq_analysis_cache_key
205
- UNIQUE (network_id, analysis_type, analysis_key),
206
- CONSTRAINT valid_analysis_status
207
- CHECK (status IN ('completed', 'failed', 'in_progress', 'stale')),
208
- CONSTRAINT valid_result_format
209
- CHECK (result_format IN ('json', 'parquet', 'csv', 'binary', 'text'))
210
- );
211
-
212
- -- Optimized indexes for cache operations
213
- CREATE INDEX idx_analysis_cache_network ON network_analysis_cache(network_id);
214
- CREATE INDEX idx_analysis_cache_type ON network_analysis_cache(network_id, analysis_type);
215
- CREATE INDEX idx_analysis_cache_key ON network_analysis_cache(analysis_key);
216
- CREATE INDEX idx_analysis_cache_status ON network_analysis_cache(status);
217
- CREATE INDEX idx_analysis_cache_expires ON network_analysis_cache(expires_at);
218
- CREATE INDEX idx_analysis_cache_accessed ON network_analysis_cache(last_accessed);
219
- CREATE INDEX idx_analysis_cache_created ON network_analysis_cache(created_at);
220
- CREATE INDEX idx_analysis_cache_size ON network_analysis_cache(result_size_bytes);
221
92
 
222
93
  -- ============================================================================
223
94
  -- SOLVE RESULTS AND STATISTICS
224
95
  -- ============================================================================
225
96
 
226
- -- Drop the old optimization_results table
227
- DROP TABLE IF EXISTS optimization_results;
228
-
229
97
  -- Network solve results - stores solver outputs and statistics
230
98
  -- This is where PyPSA solve results are stored after successful solves
231
99
  CREATE TABLE network_solve_results (
@@ -250,11 +118,9 @@ CREATE TABLE network_solve_results (
250
118
  FOREIGN KEY (scenario_id) REFERENCES scenarios(id) ON DELETE CASCADE
251
119
  );
252
120
 
253
- -- Indexes for performance
121
+ -- Minimal indexes for performance
254
122
  CREATE INDEX idx_solve_results_network ON network_solve_results(network_id);
255
123
  CREATE INDEX idx_solve_results_scenario ON network_solve_results(scenario_id);
256
- CREATE INDEX idx_solve_results_status ON network_solve_results(solve_status);
257
- CREATE INDEX idx_solve_results_solved_at ON network_solve_results(solved_at);
258
124
 
259
125
  -- ============================================================================
260
126
  -- YEAR-BASED SOLVE RESULTS
@@ -284,12 +150,9 @@ CREATE TABLE network_solve_results_by_year (
284
150
  CONSTRAINT valid_year CHECK (year >= 1900 AND year <= 2100)
285
151
  );
286
152
 
287
- -- Indexes for performance
153
+ -- Minimal indexes for performance
288
154
  CREATE INDEX idx_solve_results_year_network ON network_solve_results_by_year(network_id);
289
155
  CREATE INDEX idx_solve_results_year_scenario ON network_solve_results_by_year(scenario_id);
290
- CREATE INDEX idx_solve_results_year_year ON network_solve_results_by_year(year);
291
- CREATE INDEX idx_solve_results_year_network_scenario ON network_solve_results_by_year(network_id, scenario_id);
292
- CREATE INDEX idx_solve_results_year_created_at ON network_solve_results_by_year(created_at);
293
156
 
294
157
  -- Optional: Registry of solve type schemas for frontend introspection
295
158
  CREATE TABLE solve_type_schemas (
@@ -300,178 +163,11 @@ CREATE TABLE solve_type_schemas (
300
163
  created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
301
164
  );
302
165
 
303
- /*
304
- SUGGESTED JSON STRUCTURE FOR results_json:
305
-
306
- For PyPSA optimization (solve_type = 'pypsa_optimization'):
307
- {
308
- "core_summary": {
309
- "total_generation_mwh": 12500.0,
310
- "total_demand_mwh": 12000.0,
311
- "total_cost": 1500000.0,
312
- "load_factor": 0.96,
313
- "unserved_energy_mwh": 0.0
314
- },
315
-
316
- "pypsa_statistics": {
317
- "energy_balance": {
318
- "gas": 5000.0,
319
- "wind": 7500.0,
320
- "solar": 0.0
321
- },
322
- "supply_by_carrier": {
323
- "gas": {"total_mwh": 5000.0, "capacity_factor": 0.85},
324
- "wind": {"total_mwh": 7500.0, "capacity_factor": 0.42}
325
- },
326
- "demand_by_carrier": {
327
- "electricity": 12000.0
328
- },
329
- "capacity_factors": {
330
- "generator_gas_001": 0.85,
331
- "generator_wind_001": 0.42
332
- },
333
- "curtailment": {
334
- "wind": 250.0,
335
- "solar": 0.0
336
- },
337
- "transmission_utilization": {
338
- "line_001": 0.75,
339
- "line_002": 0.32
340
- }
341
- },
342
-
343
- "custom_statistics": {
344
- "emissions_by_carrier": {
345
- "gas": 2500.0,
346
- "wind": 0.0,
347
- "solar": 0.0
348
- },
349
- "total_emissions_tons_co2": 2500.0,
350
- "average_price_per_mwh": 125.0,
351
- "peak_demand_mw": 2500.0,
352
- "renewable_fraction": 0.6
353
- },
354
-
355
- "runtime_info": {
356
- "build_time_seconds": 5.2,
357
- "solve_time_seconds": 45.1,
358
- "result_processing_seconds": 2.3,
359
- "component_count": 150,
360
- "variable_count": 8760,
361
- "constraint_count": 12500,
362
- "memory_usage_mb": 256.5
363
- },
364
-
365
- "solver_info": {
366
- "solver_name": "highs",
367
- "solver_version": "1.6.0",
368
- "solver_options": {"presolve": "on", "parallel": "on"},
369
- "termination_condition": "optimal",
370
- "iterations": 1247,
371
- "barrier_iterations": null
372
- }
373
- }
374
-
375
- For Monte Carlo sampling (solve_type = 'monte_carlo'):
376
- {
377
- "core_summary": {
378
- "scenario_count": 1000,
379
- "convergence_achieved": true,
380
- "confidence_level": 0.95
381
- },
382
-
383
- "probability_distributions": {
384
- "total_cost": {
385
- "mean": 1500000.0,
386
- "std": 150000.0,
387
- "p05": 1250000.0,
388
- "p50": 1500000.0,
389
- "p95": 1750000.0
390
- },
391
- "unserved_energy": {
392
- "mean": 12.5,
393
- "std": 25.2,
394
- "p05": 0.0,
395
- "p50": 0.0,
396
- "p95": 75.0
397
- }
398
- },
399
-
400
- "sensitivity_analysis": {
401
- "most_influential_parameters": [
402
- {"parameter": "wind_capacity", "sensitivity": 0.85},
403
- {"parameter": "fuel_price", "sensitivity": 0.72}
404
- ]
405
- },
406
-
407
- "runtime_info": {
408
- "total_runtime_seconds": 3600.0,
409
- "scenarios_per_second": 0.28
410
- }
411
- }
412
-
413
- For sensitivity analysis (solve_type = 'sensitivity'):
414
- {
415
- "core_summary": {
416
- "parameters_analyzed": 15,
417
- "base_case_objective": 1500000.0
418
- },
419
-
420
- "parameter_sensitivities": {
421
- "fuel_cost_gas": {
422
- "sensitivity_coefficient": 0.85,
423
- "objective_range": [1200000.0, 1800000.0],
424
- "parameter_range": [50.0, 150.0]
425
- },
426
- "wind_capacity": {
427
- "sensitivity_coefficient": -0.72,
428
- "objective_range": [1300000.0, 1700000.0],
429
- "parameter_range": [1000.0, 3000.0]
430
- }
431
- },
432
-
433
- "tornado_chart_data": [
434
- {"parameter": "fuel_cost_gas", "low": -300000.0, "high": 300000.0},
435
- {"parameter": "wind_capacity", "low": -200000.0, "high": 200000.0}
436
- ]
437
- }
438
- */
439
-
440
166
  -- ============================================================================
441
- -- DATA MANAGEMENT TRIGGERS
167
+ -- UTILITY VIEWS (SIMPLIFIED)
442
168
  -- ============================================================================
443
169
 
444
- -- Note: Access tracking for analysis cache would need to be handled in application code
445
- -- SQLite doesn't support AFTER SELECT triggers
446
-
447
- -- Note: Result size calculation should be handled in application code
448
- -- Cannot modify NEW values in SQLite BEFORE INSERT triggers
449
-
450
- -- Note: Timestamp updates should be handled in application code or with DEFAULT CURRENT_TIMESTAMP
451
- -- SQLite triggers cannot update the same record being modified without recursion issues
452
-
453
- -- ============================================================================
454
- -- UTILITY VIEWS
455
- -- ============================================================================
456
-
457
- -- View for network analysis summary
458
- CREATE VIEW network_analysis_summary AS
459
- SELECT
460
- n.id as network_id,
461
- n.name as network_name,
462
- COUNT(DISTINCT nac.analysis_type) as analysis_types_count,
463
- COUNT(nac.id) as total_cache_entries,
464
- SUM(nac.hit_count) as total_cache_hits,
465
- SUM(nac.result_size_bytes) as total_cache_size_bytes,
466
- MAX(nac.last_accessed) as last_analysis_accessed,
467
- COUNT(or1.id) as optimization_runs_count,
468
- MAX(or1.created_at) as last_optimization_run
469
- FROM networks n
470
- LEFT JOIN network_analysis_cache nac ON n.id = nac.network_id
471
- LEFT JOIN optimization_results or1 ON n.id = or1.network_id
472
- GROUP BY n.id, n.name;
473
-
474
- -- View for recent network activity
170
+ -- View for recent network activity (simplified without audit/cache tables)
475
171
  CREATE VIEW network_activity_summary AS
476
172
  SELECT
477
173
  n.id as network_id,
@@ -501,7 +197,7 @@ SELECT
501
197
  c.*,
502
198
  b.name as bus_name
503
199
  FROM components c
504
- LEFT JOIN components b ON json_extract(c.connectivity, '$.bus_id') = b.id AND b.component_type = 'BUS'
200
+ LEFT JOIN components b ON c.bus_id = b.id AND b.component_type = 'BUS'
505
201
  WHERE c.component_type IN ('GENERATOR', 'LOAD', 'STORAGE_UNIT', 'STORE');
506
202
 
507
203
  -- View for components with dual bus connections (lines, links)
@@ -511,8 +207,8 @@ SELECT
511
207
  b0.name as bus0_name,
512
208
  b1.name as bus1_name
513
209
  FROM components c
514
- LEFT JOIN components b0 ON json_extract(c.connectivity, '$.bus0_id') = b0.id AND b0.component_type = 'BUS'
515
- LEFT JOIN components b1 ON json_extract(c.connectivity, '$.bus1_id') = b1.id AND b1.component_type = 'BUS'
210
+ LEFT JOIN components b0 ON c.bus0_id = b0.id AND b0.component_type = 'BUS'
211
+ LEFT JOIN components b1 ON c.bus1_id = b1.id AND b1.component_type = 'BUS'
516
212
  WHERE c.component_type IN ('LINE', 'LINK');
517
213
 
518
214
  -- Unified view for all components with resolved bus connections
@@ -522,17 +218,17 @@ SELECT
522
218
  CASE
523
219
  WHEN c.component_type = 'BUS' THEN NULL
524
220
  WHEN c.component_type IN ('GENERATOR', 'LOAD', 'STORAGE_UNIT', 'STORE') THEN
525
- (SELECT b.name FROM components b WHERE b.id = json_extract(c.connectivity, '$.bus_id') AND b.component_type = 'BUS')
221
+ (SELECT b.name FROM components b WHERE b.id = c.bus_id AND b.component_type = 'BUS')
526
222
  ELSE NULL
527
223
  END as bus_name,
528
224
  CASE
529
225
  WHEN c.component_type IN ('LINE', 'LINK') THEN
530
- (SELECT b.name FROM components b WHERE b.id = json_extract(c.connectivity, '$.bus0_id') AND b.component_type = 'BUS')
226
+ (SELECT b.name FROM components b WHERE b.id = c.bus0_id AND b.component_type = 'BUS')
531
227
  ELSE NULL
532
228
  END as bus0_name,
533
229
  CASE
534
230
  WHEN c.component_type IN ('LINE', 'LINK') THEN
535
- (SELECT b.name FROM components b WHERE b.id = json_extract(c.connectivity, '$.bus1_id') AND b.component_type = 'BUS')
231
+ (SELECT b.name FROM components b WHERE b.id = c.bus1_id AND b.component_type = 'BUS')
536
232
  ELSE NULL
537
233
  END as bus1_name
538
234
  FROM components c;
@@ -0,0 +1,5 @@
1
+ """
2
+ Data sources for PyConvexity.
3
+
4
+ This module contains integrations with external energy data sources.
5
+ """