pyconvexity 0.3.8.post4__tar.gz → 0.3.8.post5__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {pyconvexity-0.3.8.post4 → pyconvexity-0.3.8.post5}/PKG-INFO +1 -1
- {pyconvexity-0.3.8.post4 → pyconvexity-0.3.8.post5}/pyproject.toml +2 -2
- pyconvexity-0.3.8.post5/src/pyconvexity/_version.py +1 -0
- {pyconvexity-0.3.8.post4 → pyconvexity-0.3.8.post5}/src/pyconvexity/data/schema/01_core_schema.sql +51 -0
- pyconvexity-0.3.8.post5/src/pyconvexity/data/schema/migrate_add_geometries.sql +73 -0
- pyconvexity-0.3.8.post5/src/pyconvexity/models/__init__.py +87 -0
- pyconvexity-0.3.8.post5/src/pyconvexity/models/carriers.py +156 -0
- {pyconvexity-0.3.8.post4 → pyconvexity-0.3.8.post5}/src/pyconvexity/models/components.py +120 -0
- {pyconvexity-0.3.8.post4 → pyconvexity-0.3.8.post5}/src/pyconvexity/models/network.py +67 -1
- pyconvexity-0.3.8.post5/src/pyconvexity/models/results.py +138 -0
- pyconvexity-0.3.8.post5/src/pyconvexity/models/scenarios.py +165 -0
- {pyconvexity-0.3.8.post4 → pyconvexity-0.3.8.post5}/src/pyconvexity/solvers/pypsa/api.py +6 -1
- {pyconvexity-0.3.8.post4 → pyconvexity-0.3.8.post5}/src/pyconvexity/solvers/pypsa/solver.py +20 -74
- {pyconvexity-0.3.8.post4 → pyconvexity-0.3.8.post5}/src/pyconvexity.egg-info/PKG-INFO +1 -1
- {pyconvexity-0.3.8.post4 → pyconvexity-0.3.8.post5}/src/pyconvexity.egg-info/SOURCES.txt +3 -0
- pyconvexity-0.3.8.post4/src/pyconvexity/_version.py +0 -1
- pyconvexity-0.3.8.post4/src/pyconvexity/models/__init__.py +0 -43
- pyconvexity-0.3.8.post4/src/pyconvexity/models/scenarios.py +0 -177
- {pyconvexity-0.3.8.post4 → pyconvexity-0.3.8.post5}/README.md +0 -0
- {pyconvexity-0.3.8.post4 → pyconvexity-0.3.8.post5}/setup.cfg +0 -0
- {pyconvexity-0.3.8.post4 → pyconvexity-0.3.8.post5}/src/pyconvexity/__init__.py +0 -0
- {pyconvexity-0.3.8.post4 → pyconvexity-0.3.8.post5}/src/pyconvexity/core/__init__.py +0 -0
- {pyconvexity-0.3.8.post4 → pyconvexity-0.3.8.post5}/src/pyconvexity/core/database.py +0 -0
- {pyconvexity-0.3.8.post4 → pyconvexity-0.3.8.post5}/src/pyconvexity/core/errors.py +0 -0
- {pyconvexity-0.3.8.post4 → pyconvexity-0.3.8.post5}/src/pyconvexity/core/types.py +0 -0
- {pyconvexity-0.3.8.post4 → pyconvexity-0.3.8.post5}/src/pyconvexity/data/README.md +0 -0
- {pyconvexity-0.3.8.post4 → pyconvexity-0.3.8.post5}/src/pyconvexity/data/__init__.py +0 -0
- {pyconvexity-0.3.8.post4 → pyconvexity-0.3.8.post5}/src/pyconvexity/data/__pycache__/__init__.cpython-313.pyc +0 -0
- {pyconvexity-0.3.8.post4 → pyconvexity-0.3.8.post5}/src/pyconvexity/data/loaders/__init__.py +0 -0
- {pyconvexity-0.3.8.post4 → pyconvexity-0.3.8.post5}/src/pyconvexity/data/loaders/__pycache__/__init__.cpython-313.pyc +0 -0
- {pyconvexity-0.3.8.post4 → pyconvexity-0.3.8.post5}/src/pyconvexity/data/loaders/__pycache__/cache.cpython-313.pyc +0 -0
- {pyconvexity-0.3.8.post4 → pyconvexity-0.3.8.post5}/src/pyconvexity/data/loaders/cache.py +0 -0
- {pyconvexity-0.3.8.post4 → pyconvexity-0.3.8.post5}/src/pyconvexity/data/schema/02_data_metadata.sql +0 -0
- {pyconvexity-0.3.8.post4 → pyconvexity-0.3.8.post5}/src/pyconvexity/data/schema/03_validation_data.sql +0 -0
- {pyconvexity-0.3.8.post4 → pyconvexity-0.3.8.post5}/src/pyconvexity/data/schema/04_scenario_schema.sql +0 -0
- {pyconvexity-0.3.8.post4 → pyconvexity-0.3.8.post5}/src/pyconvexity/data/sources/__init__.py +0 -0
- {pyconvexity-0.3.8.post4 → pyconvexity-0.3.8.post5}/src/pyconvexity/data/sources/__pycache__/__init__.cpython-313.pyc +0 -0
- {pyconvexity-0.3.8.post4 → pyconvexity-0.3.8.post5}/src/pyconvexity/data/sources/__pycache__/gem.cpython-313.pyc +0 -0
- {pyconvexity-0.3.8.post4 → pyconvexity-0.3.8.post5}/src/pyconvexity/data/sources/gem.py +0 -0
- {pyconvexity-0.3.8.post4 → pyconvexity-0.3.8.post5}/src/pyconvexity/io/__init__.py +0 -0
- {pyconvexity-0.3.8.post4 → pyconvexity-0.3.8.post5}/src/pyconvexity/io/excel_exporter.py +0 -0
- {pyconvexity-0.3.8.post4 → pyconvexity-0.3.8.post5}/src/pyconvexity/io/excel_importer.py +0 -0
- {pyconvexity-0.3.8.post4 → pyconvexity-0.3.8.post5}/src/pyconvexity/io/netcdf_exporter.py +0 -0
- {pyconvexity-0.3.8.post4 → pyconvexity-0.3.8.post5}/src/pyconvexity/io/netcdf_importer.py +0 -0
- {pyconvexity-0.3.8.post4 → pyconvexity-0.3.8.post5}/src/pyconvexity/models/attributes.py +0 -0
- {pyconvexity-0.3.8.post4 → pyconvexity-0.3.8.post5}/src/pyconvexity/solvers/__init__.py +0 -0
- {pyconvexity-0.3.8.post4 → pyconvexity-0.3.8.post5}/src/pyconvexity/solvers/pypsa/__init__.py +0 -0
- {pyconvexity-0.3.8.post4 → pyconvexity-0.3.8.post5}/src/pyconvexity/solvers/pypsa/batch_loader.py +0 -0
- {pyconvexity-0.3.8.post4 → pyconvexity-0.3.8.post5}/src/pyconvexity/solvers/pypsa/builder.py +0 -0
- {pyconvexity-0.3.8.post4 → pyconvexity-0.3.8.post5}/src/pyconvexity/solvers/pypsa/constraints.py +0 -0
- {pyconvexity-0.3.8.post4 → pyconvexity-0.3.8.post5}/src/pyconvexity/solvers/pypsa/storage.py +0 -0
- {pyconvexity-0.3.8.post4 → pyconvexity-0.3.8.post5}/src/pyconvexity/timeseries.py +0 -0
- {pyconvexity-0.3.8.post4 → pyconvexity-0.3.8.post5}/src/pyconvexity/validation/__init__.py +0 -0
- {pyconvexity-0.3.8.post4 → pyconvexity-0.3.8.post5}/src/pyconvexity/validation/rules.py +0 -0
- {pyconvexity-0.3.8.post4 → pyconvexity-0.3.8.post5}/src/pyconvexity.egg-info/dependency_links.txt +0 -0
- {pyconvexity-0.3.8.post4 → pyconvexity-0.3.8.post5}/src/pyconvexity.egg-info/requires.txt +0 -0
- {pyconvexity-0.3.8.post4 → pyconvexity-0.3.8.post5}/src/pyconvexity.egg-info/top_level.txt +0 -0
- {pyconvexity-0.3.8.post4 → pyconvexity-0.3.8.post5}/tests/test_core_types.py +0 -0
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "pyconvexity"
|
|
7
|
-
version = "0.3.
|
|
7
|
+
version = "0.3.8post5"
|
|
8
8
|
description = "Python library for energy system modeling and optimization with PyPSA"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = {text = "MIT"}
|
|
@@ -81,7 +81,7 @@ profile = "black"
|
|
|
81
81
|
line_length = 100
|
|
82
82
|
|
|
83
83
|
[tool.mypy]
|
|
84
|
-
python_version = "0.3.
|
|
84
|
+
python_version = "0.3.8post5"
|
|
85
85
|
warn_return_any = true
|
|
86
86
|
warn_unused_configs = true
|
|
87
87
|
disallow_untyped_defs = true
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.3.8post5"
|
{pyconvexity-0.3.8.post4 → pyconvexity-0.3.8.post5}/src/pyconvexity/data/schema/01_core_schema.sql
RENAMED
|
@@ -361,6 +361,57 @@ BEGIN
|
|
|
361
361
|
WHERE id = NEW.component_id;
|
|
362
362
|
END;
|
|
363
363
|
|
|
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
|
+
|
|
364
415
|
-- ============================================================================
|
|
365
416
|
-- NETWORK CONFIGURATION
|
|
366
417
|
-- ============================================================================
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
-- ============================================================================
|
|
2
|
+
-- MIGRATION: Add Component Geometries Support
|
|
3
|
+
-- Adds the component_geometries table to existing databases
|
|
4
|
+
-- This migration is safe and backwards compatible - existing functionality is unchanged
|
|
5
|
+
-- ============================================================================
|
|
6
|
+
|
|
7
|
+
-- Check if the table already exists before creating
|
|
8
|
+
CREATE TABLE IF NOT EXISTS component_geometries (
|
|
9
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
10
|
+
component_id INTEGER NOT NULL UNIQUE,
|
|
11
|
+
|
|
12
|
+
-- GeoJSON geometry stored as JSON text
|
|
13
|
+
-- Supports: Point, LineString, Polygon, MultiPolygon, MultiPoint, MultiLineString, GeometryCollection
|
|
14
|
+
geometry TEXT NOT NULL,
|
|
15
|
+
|
|
16
|
+
-- Cache the geometry type for faster queries and validation
|
|
17
|
+
geometry_type TEXT NOT NULL CHECK (geometry_type IN (
|
|
18
|
+
'Point', 'LineString', 'Polygon', 'MultiPolygon',
|
|
19
|
+
'MultiPoint', 'MultiLineString', 'GeometryCollection'
|
|
20
|
+
)),
|
|
21
|
+
|
|
22
|
+
-- Cache bounding box for spatial indexing and quick filtering
|
|
23
|
+
bbox_min_lng REAL,
|
|
24
|
+
bbox_min_lat REAL,
|
|
25
|
+
bbox_max_lng REAL,
|
|
26
|
+
bbox_max_lat REAL,
|
|
27
|
+
|
|
28
|
+
-- Metadata
|
|
29
|
+
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
30
|
+
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
31
|
+
|
|
32
|
+
CONSTRAINT fk_geometry_component
|
|
33
|
+
FOREIGN KEY (component_id) REFERENCES components(id) ON DELETE CASCADE
|
|
34
|
+
);
|
|
35
|
+
|
|
36
|
+
-- Create indexes if they don't exist
|
|
37
|
+
CREATE INDEX IF NOT EXISTS idx_component_geometries_component
|
|
38
|
+
ON component_geometries(component_id);
|
|
39
|
+
|
|
40
|
+
CREATE INDEX IF NOT EXISTS idx_component_geometries_type
|
|
41
|
+
ON component_geometries(geometry_type);
|
|
42
|
+
|
|
43
|
+
CREATE INDEX IF NOT EXISTS idx_component_geometries_bbox
|
|
44
|
+
ON component_geometries(bbox_min_lng, bbox_min_lat, bbox_max_lng, bbox_max_lat);
|
|
45
|
+
|
|
46
|
+
-- Create trigger for automatic timestamp updates
|
|
47
|
+
DROP TRIGGER IF EXISTS update_component_geometries_timestamp;
|
|
48
|
+
|
|
49
|
+
CREATE TRIGGER update_component_geometries_timestamp
|
|
50
|
+
BEFORE UPDATE ON component_geometries
|
|
51
|
+
FOR EACH ROW
|
|
52
|
+
BEGIN
|
|
53
|
+
UPDATE component_geometries
|
|
54
|
+
SET updated_at = CURRENT_TIMESTAMP
|
|
55
|
+
WHERE id = NEW.id;
|
|
56
|
+
END;
|
|
57
|
+
|
|
58
|
+
-- Update schema version
|
|
59
|
+
UPDATE system_metadata
|
|
60
|
+
SET value = '2.5.0',
|
|
61
|
+
updated_at = CURRENT_TIMESTAMP,
|
|
62
|
+
description = 'Database schema version - Added component_geometries table'
|
|
63
|
+
WHERE key = 'schema_version';
|
|
64
|
+
|
|
65
|
+
-- Add metadata about the migration
|
|
66
|
+
INSERT OR REPLACE INTO system_metadata (key, value, description)
|
|
67
|
+
VALUES ('geometries_migration_applied', datetime('now'), 'Timestamp when component geometries migration was applied');
|
|
68
|
+
|
|
69
|
+
-- Verify the migration
|
|
70
|
+
SELECT
|
|
71
|
+
'Migration completed successfully!' as message,
|
|
72
|
+
COUNT(*) as existing_geometries
|
|
73
|
+
FROM component_geometries;
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Model management module for PyConvexity.
|
|
3
|
+
|
|
4
|
+
Contains high-level operations for networks, components, and attributes.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from pyconvexity.models.components import (
|
|
8
|
+
get_component_type, get_component, list_components_by_type,
|
|
9
|
+
insert_component, create_component, update_component, delete_component,
|
|
10
|
+
list_component_attributes, get_default_carrier_id, get_bus_name_to_id_map,
|
|
11
|
+
get_component_by_name, get_component_id, component_exists, get_component_carrier_map
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
from pyconvexity.models.attributes import (
|
|
15
|
+
set_static_attribute, set_timeseries_attribute, get_attribute, delete_attribute
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
from pyconvexity.models.network import (
|
|
19
|
+
create_network, get_network_info, get_network_time_periods, list_networks,
|
|
20
|
+
create_carrier, list_carriers, get_network_config, set_network_config,
|
|
21
|
+
get_component_counts, get_master_scenario_id, resolve_scenario_id,
|
|
22
|
+
get_first_network, get_network_by_name
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
# Import from new modules
|
|
26
|
+
from pyconvexity.models.scenarios import (
|
|
27
|
+
list_scenarios as list_scenarios_new,
|
|
28
|
+
get_scenario_by_name, get_scenario_by_id, get_master_scenario
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
from pyconvexity.models.results import (
|
|
32
|
+
get_solve_results, get_yearly_results
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
from pyconvexity.models.carriers import (
|
|
36
|
+
list_carriers as list_carriers_new,
|
|
37
|
+
get_carrier_by_name, get_carrier_by_id, get_carrier_colors
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
# Try to import old scenarios functions if they exist
|
|
41
|
+
try:
|
|
42
|
+
from pyconvexity.models.scenarios_old import (
|
|
43
|
+
create_scenario, list_scenarios as list_scenarios_old,
|
|
44
|
+
get_scenario, delete_scenario
|
|
45
|
+
)
|
|
46
|
+
# Use old functions as primary for backward compatibility
|
|
47
|
+
list_scenarios_primary = list_scenarios_old
|
|
48
|
+
except ImportError:
|
|
49
|
+
# Old module doesn't exist, use new functions
|
|
50
|
+
list_scenarios_primary = list_scenarios_new
|
|
51
|
+
# Create dummy functions for backward compatibility
|
|
52
|
+
def create_scenario(*args, **kwargs):
|
|
53
|
+
raise NotImplementedError("create_scenario not yet implemented in new API")
|
|
54
|
+
def get_scenario(*args, **kwargs):
|
|
55
|
+
return get_scenario_by_id(*args, **kwargs)
|
|
56
|
+
def delete_scenario(*args, **kwargs):
|
|
57
|
+
raise NotImplementedError("delete_scenario not yet implemented in new API")
|
|
58
|
+
|
|
59
|
+
__all__ = [
|
|
60
|
+
# Component operations
|
|
61
|
+
"get_component_type", "get_component", "list_components_by_type",
|
|
62
|
+
"insert_component", "create_component", "update_component", "delete_component",
|
|
63
|
+
"list_component_attributes", "get_default_carrier_id", "get_bus_name_to_id_map",
|
|
64
|
+
"get_component_by_name", "get_component_id", "component_exists", "get_component_carrier_map",
|
|
65
|
+
|
|
66
|
+
# Attribute operations
|
|
67
|
+
"set_static_attribute", "set_timeseries_attribute", "get_attribute", "delete_attribute",
|
|
68
|
+
|
|
69
|
+
# Network operations
|
|
70
|
+
"create_network", "get_network_info", "get_network_time_periods", "list_networks",
|
|
71
|
+
"create_carrier", "list_carriers", "get_network_config", "set_network_config",
|
|
72
|
+
"get_component_counts", "get_master_scenario_id", "resolve_scenario_id",
|
|
73
|
+
"get_first_network", "get_network_by_name",
|
|
74
|
+
|
|
75
|
+
# Scenario operations (backward compatible)
|
|
76
|
+
"create_scenario", "list_scenarios_primary", "get_scenario", "delete_scenario",
|
|
77
|
+
"list_scenarios_new", "get_scenario_by_name", "get_scenario_by_id", "get_master_scenario",
|
|
78
|
+
|
|
79
|
+
# Results operations
|
|
80
|
+
"get_solve_results", "get_yearly_results",
|
|
81
|
+
|
|
82
|
+
# Carrier operations
|
|
83
|
+
"list_carriers_new", "get_carrier_by_name", "get_carrier_by_id", "get_carrier_colors",
|
|
84
|
+
]
|
|
85
|
+
|
|
86
|
+
# Expose primary list_scenarios for convenience
|
|
87
|
+
list_scenarios = list_scenarios_primary
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Carrier management operations for PyConvexity.
|
|
3
|
+
|
|
4
|
+
Provides operations for querying carriers and their properties.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import sqlite3
|
|
8
|
+
import logging
|
|
9
|
+
from typing import Dict, List, Optional
|
|
10
|
+
from dataclasses import dataclass
|
|
11
|
+
|
|
12
|
+
from pyconvexity.core.errors import ValidationError
|
|
13
|
+
|
|
14
|
+
logger = logging.getLogger(__name__)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@dataclass
|
|
18
|
+
class Carrier:
|
|
19
|
+
"""Represents an energy carrier in the network."""
|
|
20
|
+
id: int
|
|
21
|
+
network_id: int
|
|
22
|
+
name: str
|
|
23
|
+
co2_emissions: float
|
|
24
|
+
color: Optional[str]
|
|
25
|
+
nice_name: Optional[str]
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def list_carriers(conn: sqlite3.Connection, network_id: int) -> List[Carrier]:
|
|
29
|
+
"""
|
|
30
|
+
List all carriers for a network.
|
|
31
|
+
|
|
32
|
+
Args:
|
|
33
|
+
conn: Database connection
|
|
34
|
+
network_id: Network ID
|
|
35
|
+
|
|
36
|
+
Returns:
|
|
37
|
+
List of Carrier objects ordered by name
|
|
38
|
+
"""
|
|
39
|
+
cursor = conn.execute("""
|
|
40
|
+
SELECT id, network_id, name, co2_emissions, color, nice_name
|
|
41
|
+
FROM carriers
|
|
42
|
+
WHERE network_id = ?
|
|
43
|
+
ORDER BY name
|
|
44
|
+
""", (network_id,))
|
|
45
|
+
|
|
46
|
+
carriers = []
|
|
47
|
+
for row in cursor.fetchall():
|
|
48
|
+
carriers.append(Carrier(
|
|
49
|
+
id=row[0],
|
|
50
|
+
network_id=row[1],
|
|
51
|
+
name=row[2],
|
|
52
|
+
co2_emissions=row[3] or 0.0,
|
|
53
|
+
color=row[4],
|
|
54
|
+
nice_name=row[5]
|
|
55
|
+
))
|
|
56
|
+
|
|
57
|
+
return carriers
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def get_carrier_by_name(conn: sqlite3.Connection, network_id: int, name: str) -> Carrier:
|
|
61
|
+
"""
|
|
62
|
+
Get a carrier by name.
|
|
63
|
+
|
|
64
|
+
Args:
|
|
65
|
+
conn: Database connection
|
|
66
|
+
network_id: Network ID
|
|
67
|
+
name: Carrier name
|
|
68
|
+
|
|
69
|
+
Returns:
|
|
70
|
+
Carrier object
|
|
71
|
+
|
|
72
|
+
Raises:
|
|
73
|
+
ValidationError: If carrier doesn't exist
|
|
74
|
+
"""
|
|
75
|
+
cursor = conn.execute("""
|
|
76
|
+
SELECT id, network_id, name, co2_emissions, color, nice_name
|
|
77
|
+
FROM carriers
|
|
78
|
+
WHERE network_id = ? AND name = ?
|
|
79
|
+
""", (network_id, name))
|
|
80
|
+
|
|
81
|
+
row = cursor.fetchone()
|
|
82
|
+
if not row:
|
|
83
|
+
raise ValidationError(f"Carrier '{name}' not found for network {network_id}")
|
|
84
|
+
|
|
85
|
+
return Carrier(
|
|
86
|
+
id=row[0],
|
|
87
|
+
network_id=row[1],
|
|
88
|
+
name=row[2],
|
|
89
|
+
co2_emissions=row[3] or 0.0,
|
|
90
|
+
color=row[4],
|
|
91
|
+
nice_name=row[5]
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def get_carrier_by_id(conn: sqlite3.Connection, carrier_id: int) -> Carrier:
|
|
96
|
+
"""
|
|
97
|
+
Get a carrier by ID.
|
|
98
|
+
|
|
99
|
+
Args:
|
|
100
|
+
conn: Database connection
|
|
101
|
+
carrier_id: Carrier ID
|
|
102
|
+
|
|
103
|
+
Returns:
|
|
104
|
+
Carrier object
|
|
105
|
+
|
|
106
|
+
Raises:
|
|
107
|
+
ValidationError: If carrier doesn't exist
|
|
108
|
+
"""
|
|
109
|
+
cursor = conn.execute("""
|
|
110
|
+
SELECT id, network_id, name, co2_emissions, color, nice_name
|
|
111
|
+
FROM carriers
|
|
112
|
+
WHERE id = ?
|
|
113
|
+
""", (carrier_id,))
|
|
114
|
+
|
|
115
|
+
row = cursor.fetchone()
|
|
116
|
+
if not row:
|
|
117
|
+
raise ValidationError(f"Carrier with ID {carrier_id} not found")
|
|
118
|
+
|
|
119
|
+
return Carrier(
|
|
120
|
+
id=row[0],
|
|
121
|
+
network_id=row[1],
|
|
122
|
+
name=row[2],
|
|
123
|
+
co2_emissions=row[3] or 0.0,
|
|
124
|
+
color=row[4],
|
|
125
|
+
nice_name=row[5]
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def get_carrier_colors(conn: sqlite3.Connection, network_id: int) -> Dict[str, str]:
|
|
130
|
+
"""
|
|
131
|
+
Get carrier colors for visualization.
|
|
132
|
+
|
|
133
|
+
Args:
|
|
134
|
+
conn: Database connection
|
|
135
|
+
network_id: Network ID
|
|
136
|
+
|
|
137
|
+
Returns:
|
|
138
|
+
Dictionary mapping carrier names to color strings
|
|
139
|
+
"""
|
|
140
|
+
cursor = conn.execute("""
|
|
141
|
+
SELECT name, color
|
|
142
|
+
FROM carriers
|
|
143
|
+
WHERE network_id = ?
|
|
144
|
+
""", (network_id,))
|
|
145
|
+
|
|
146
|
+
colors = {}
|
|
147
|
+
for row in cursor.fetchall():
|
|
148
|
+
if row[1]: # Only include if color is defined
|
|
149
|
+
colors[row[0]] = row[1]
|
|
150
|
+
|
|
151
|
+
# Add default color for Unmet Load if not present
|
|
152
|
+
if 'Unmet Load' not in colors:
|
|
153
|
+
colors['Unmet Load'] = '#FF0000'
|
|
154
|
+
|
|
155
|
+
return colors
|
|
156
|
+
|
|
@@ -364,6 +364,126 @@ def list_component_attributes(
|
|
|
364
364
|
return [row[0] for row in cursor.fetchall()]
|
|
365
365
|
|
|
366
366
|
|
|
367
|
+
def get_component_by_name(conn: sqlite3.Connection, network_id: int, name: str) -> Component:
|
|
368
|
+
"""
|
|
369
|
+
Get a component by name.
|
|
370
|
+
|
|
371
|
+
Args:
|
|
372
|
+
conn: Database connection
|
|
373
|
+
network_id: Network ID
|
|
374
|
+
name: Component name
|
|
375
|
+
|
|
376
|
+
Returns:
|
|
377
|
+
Component object
|
|
378
|
+
|
|
379
|
+
Raises:
|
|
380
|
+
ComponentNotFound: If component doesn't exist
|
|
381
|
+
"""
|
|
382
|
+
cursor = conn.execute("""
|
|
383
|
+
SELECT id, network_id, component_type, name, longitude, latitude,
|
|
384
|
+
carrier_id, bus_id, bus0_id, bus1_id
|
|
385
|
+
FROM components
|
|
386
|
+
WHERE network_id = ? AND name = ?
|
|
387
|
+
""", (network_id, name))
|
|
388
|
+
|
|
389
|
+
row = cursor.fetchone()
|
|
390
|
+
if not row:
|
|
391
|
+
raise ComponentNotFound(f"Component '{name}' not found in network {network_id}")
|
|
392
|
+
|
|
393
|
+
return Component(
|
|
394
|
+
id=row[0],
|
|
395
|
+
network_id=row[1],
|
|
396
|
+
component_type=row[2],
|
|
397
|
+
name=row[3],
|
|
398
|
+
longitude=row[4],
|
|
399
|
+
latitude=row[5],
|
|
400
|
+
carrier_id=row[6],
|
|
401
|
+
bus_id=row[7],
|
|
402
|
+
bus0_id=row[8],
|
|
403
|
+
bus1_id=row[9]
|
|
404
|
+
)
|
|
405
|
+
|
|
406
|
+
|
|
407
|
+
def get_component_id(conn: sqlite3.Connection, network_id: int, name: str) -> int:
|
|
408
|
+
"""
|
|
409
|
+
Get component ID by name.
|
|
410
|
+
|
|
411
|
+
Args:
|
|
412
|
+
conn: Database connection
|
|
413
|
+
network_id: Network ID
|
|
414
|
+
name: Component name
|
|
415
|
+
|
|
416
|
+
Returns:
|
|
417
|
+
Component ID
|
|
418
|
+
|
|
419
|
+
Raises:
|
|
420
|
+
ComponentNotFound: If component doesn't exist
|
|
421
|
+
"""
|
|
422
|
+
component = get_component_by_name(conn, network_id, name)
|
|
423
|
+
return component.id
|
|
424
|
+
|
|
425
|
+
|
|
426
|
+
def component_exists(conn: sqlite3.Connection, network_id: int, name: str) -> bool:
|
|
427
|
+
"""
|
|
428
|
+
Check if a component exists.
|
|
429
|
+
|
|
430
|
+
Args:
|
|
431
|
+
conn: Database connection
|
|
432
|
+
network_id: Network ID
|
|
433
|
+
name: Component name
|
|
434
|
+
|
|
435
|
+
Returns:
|
|
436
|
+
True if component exists, False otherwise
|
|
437
|
+
"""
|
|
438
|
+
cursor = conn.execute("""
|
|
439
|
+
SELECT 1 FROM components WHERE network_id = ? AND name = ?
|
|
440
|
+
""", (network_id, name))
|
|
441
|
+
|
|
442
|
+
return cursor.fetchone() is not None
|
|
443
|
+
|
|
444
|
+
|
|
445
|
+
def get_component_carrier_map(
|
|
446
|
+
conn: sqlite3.Connection,
|
|
447
|
+
network_id: int,
|
|
448
|
+
component_type: Optional[str] = None
|
|
449
|
+
) -> Dict[str, str]:
|
|
450
|
+
"""
|
|
451
|
+
Get mapping from component names to carrier names.
|
|
452
|
+
|
|
453
|
+
Args:
|
|
454
|
+
conn: Database connection
|
|
455
|
+
network_id: Network ID
|
|
456
|
+
component_type: Optional component type filter
|
|
457
|
+
|
|
458
|
+
Returns:
|
|
459
|
+
Dictionary mapping component names to carrier names
|
|
460
|
+
"""
|
|
461
|
+
if component_type:
|
|
462
|
+
cursor = conn.execute("""
|
|
463
|
+
SELECT c.name,
|
|
464
|
+
CASE
|
|
465
|
+
WHEN c.component_type = 'UNMET_LOAD' THEN 'Unmet Load'
|
|
466
|
+
ELSE carr.name
|
|
467
|
+
END as carrier_name
|
|
468
|
+
FROM components c
|
|
469
|
+
LEFT JOIN carriers carr ON c.carrier_id = carr.id
|
|
470
|
+
WHERE c.network_id = ? AND c.component_type = ?
|
|
471
|
+
""", (network_id, component_type.upper()))
|
|
472
|
+
else:
|
|
473
|
+
cursor = conn.execute("""
|
|
474
|
+
SELECT c.name,
|
|
475
|
+
CASE
|
|
476
|
+
WHEN c.component_type = 'UNMET_LOAD' THEN 'Unmet Load'
|
|
477
|
+
ELSE carr.name
|
|
478
|
+
END as carrier_name
|
|
479
|
+
FROM components c
|
|
480
|
+
LEFT JOIN carriers carr ON c.carrier_id = carr.id
|
|
481
|
+
WHERE c.network_id = ?
|
|
482
|
+
""", (network_id,))
|
|
483
|
+
|
|
484
|
+
return {row[0]: row[1] for row in cursor.fetchall()}
|
|
485
|
+
|
|
486
|
+
|
|
367
487
|
# Helper functions
|
|
368
488
|
|
|
369
489
|
def get_default_carrier_id(
|
|
@@ -176,6 +176,72 @@ def list_networks(conn: sqlite3.Connection) -> List[Dict[str, Any]]:
|
|
|
176
176
|
return networks
|
|
177
177
|
|
|
178
178
|
|
|
179
|
+
def get_first_network(conn: sqlite3.Connection) -> Optional[Dict[str, Any]]:
|
|
180
|
+
"""
|
|
181
|
+
Get the first network (useful for single-network databases).
|
|
182
|
+
|
|
183
|
+
Args:
|
|
184
|
+
conn: Database connection
|
|
185
|
+
|
|
186
|
+
Returns:
|
|
187
|
+
Network dictionary or None if no networks exist
|
|
188
|
+
"""
|
|
189
|
+
cursor = conn.execute("""
|
|
190
|
+
SELECT id, name, description, created_at, updated_at, time_interval, time_start, time_end
|
|
191
|
+
FROM networks
|
|
192
|
+
ORDER BY created_at DESC
|
|
193
|
+
LIMIT 1
|
|
194
|
+
""")
|
|
195
|
+
|
|
196
|
+
row = cursor.fetchone()
|
|
197
|
+
if not row:
|
|
198
|
+
return None
|
|
199
|
+
|
|
200
|
+
return {
|
|
201
|
+
"id": row[0],
|
|
202
|
+
"name": row[1],
|
|
203
|
+
"description": row[2],
|
|
204
|
+
"created_at": row[3],
|
|
205
|
+
"updated_at": row[4],
|
|
206
|
+
"time_resolution": row[5],
|
|
207
|
+
"start_time": row[6],
|
|
208
|
+
"end_time": row[7],
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
def get_network_by_name(conn: sqlite3.Connection, name: str) -> Optional[Dict[str, Any]]:
|
|
213
|
+
"""
|
|
214
|
+
Get a network by name.
|
|
215
|
+
|
|
216
|
+
Args:
|
|
217
|
+
conn: Database connection
|
|
218
|
+
name: Network name
|
|
219
|
+
|
|
220
|
+
Returns:
|
|
221
|
+
Network dictionary or None if not found
|
|
222
|
+
"""
|
|
223
|
+
cursor = conn.execute("""
|
|
224
|
+
SELECT id, name, description, created_at, updated_at, time_interval, time_start, time_end
|
|
225
|
+
FROM networks
|
|
226
|
+
WHERE name = ?
|
|
227
|
+
""", (name,))
|
|
228
|
+
|
|
229
|
+
row = cursor.fetchone()
|
|
230
|
+
if not row:
|
|
231
|
+
return None
|
|
232
|
+
|
|
233
|
+
return {
|
|
234
|
+
"id": row[0],
|
|
235
|
+
"name": row[1],
|
|
236
|
+
"description": row[2],
|
|
237
|
+
"created_at": row[3],
|
|
238
|
+
"updated_at": row[4],
|
|
239
|
+
"time_resolution": row[5],
|
|
240
|
+
"start_time": row[6],
|
|
241
|
+
"end_time": row[7],
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
|
|
179
245
|
def create_carrier(
|
|
180
246
|
conn: sqlite3.Connection,
|
|
181
247
|
network_id: int,
|
|
@@ -309,7 +375,7 @@ def get_network_config(
|
|
|
309
375
|
# Apply system defaults for missing parameters
|
|
310
376
|
defaults = {
|
|
311
377
|
'unmet_load_active': True,
|
|
312
|
-
'discount_rate': 0.
|
|
378
|
+
'discount_rate': 0.0, # No discounting by default
|
|
313
379
|
'solver_name': 'default'
|
|
314
380
|
}
|
|
315
381
|
|