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.
- pyconvexity/__init__.py +57 -8
- pyconvexity/_version.py +1 -2
- pyconvexity/core/__init__.py +0 -2
- pyconvexity/core/database.py +158 -0
- pyconvexity/core/types.py +105 -18
- pyconvexity/data/README.md +101 -0
- pyconvexity/data/__init__.py +18 -0
- pyconvexity/data/__pycache__/__init__.cpython-313.pyc +0 -0
- pyconvexity/data/loaders/__init__.py +3 -0
- pyconvexity/data/loaders/__pycache__/__init__.cpython-313.pyc +0 -0
- pyconvexity/data/loaders/__pycache__/cache.cpython-313.pyc +0 -0
- pyconvexity/data/loaders/cache.py +212 -0
- pyconvexity/data/schema/01_core_schema.sql +12 -12
- pyconvexity/data/schema/02_data_metadata.sql +17 -321
- pyconvexity/data/sources/__init__.py +5 -0
- pyconvexity/data/sources/__pycache__/__init__.cpython-313.pyc +0 -0
- pyconvexity/data/sources/__pycache__/gem.cpython-313.pyc +0 -0
- pyconvexity/data/sources/gem.py +412 -0
- pyconvexity/io/__init__.py +32 -0
- pyconvexity/io/excel_exporter.py +1012 -0
- pyconvexity/io/excel_importer.py +1109 -0
- pyconvexity/io/netcdf_exporter.py +192 -0
- pyconvexity/io/netcdf_importer.py +1602 -0
- pyconvexity/models/__init__.py +7 -0
- pyconvexity/models/attributes.py +209 -72
- pyconvexity/models/components.py +3 -0
- pyconvexity/models/network.py +17 -15
- pyconvexity/models/scenarios.py +177 -0
- pyconvexity/solvers/__init__.py +29 -0
- pyconvexity/solvers/pypsa/__init__.py +24 -0
- pyconvexity/solvers/pypsa/api.py +421 -0
- pyconvexity/solvers/pypsa/batch_loader.py +304 -0
- pyconvexity/solvers/pypsa/builder.py +566 -0
- pyconvexity/solvers/pypsa/constraints.py +321 -0
- pyconvexity/solvers/pypsa/solver.py +1106 -0
- pyconvexity/solvers/pypsa/storage.py +1574 -0
- pyconvexity/timeseries.py +327 -0
- pyconvexity/validation/rules.py +2 -2
- {pyconvexity-0.1.2.dist-info → pyconvexity-0.1.4.dist-info}/METADATA +5 -2
- pyconvexity-0.1.4.dist-info/RECORD +46 -0
- pyconvexity-0.1.2.dist-info/RECORD +0 -20
- {pyconvexity-0.1.2.dist-info → pyconvexity-0.1.4.dist-info}/WHEEL +0 -0
- {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 -
|
|
38
|
-
--
|
|
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
|
-
|
|
43
|
-
|
|
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
|
|
48
|
-
|
|
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
|
-
|
|
54
|
-
|
|
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
|
-
--
|
|
4
|
-
--
|
|
5
|
-
-- Version 2.
|
|
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
|
-
--
|
|
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
|
-
--
|
|
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
|
-
--
|
|
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
|
-
--
|
|
167
|
+
-- UTILITY VIEWS (SIMPLIFIED)
|
|
442
168
|
-- ============================================================================
|
|
443
169
|
|
|
444
|
-
--
|
|
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
|
|
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
|
|
515
|
-
LEFT JOIN components b1 ON
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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;
|
|
Binary file
|
|
Binary file
|