pyconvexity 0.4.0__py3-none-any.whl → 0.4.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. pyconvexity/__init__.py +87 -46
  2. pyconvexity/_version.py +1 -1
  3. pyconvexity/core/__init__.py +3 -5
  4. pyconvexity/core/database.py +111 -103
  5. pyconvexity/core/errors.py +16 -10
  6. pyconvexity/core/types.py +61 -54
  7. pyconvexity/data/__init__.py +0 -1
  8. pyconvexity/data/loaders/cache.py +65 -64
  9. pyconvexity/data/schema/01_core_schema.sql +134 -234
  10. pyconvexity/data/schema/02_data_metadata.sql +38 -168
  11. pyconvexity/data/schema/03_validation_data.sql +327 -264
  12. pyconvexity/data/sources/gem.py +169 -139
  13. pyconvexity/io/__init__.py +4 -10
  14. pyconvexity/io/excel_exporter.py +694 -480
  15. pyconvexity/io/excel_importer.py +817 -545
  16. pyconvexity/io/netcdf_exporter.py +66 -61
  17. pyconvexity/io/netcdf_importer.py +850 -619
  18. pyconvexity/models/__init__.py +109 -59
  19. pyconvexity/models/attributes.py +197 -178
  20. pyconvexity/models/carriers.py +70 -67
  21. pyconvexity/models/components.py +260 -236
  22. pyconvexity/models/network.py +202 -284
  23. pyconvexity/models/results.py +65 -55
  24. pyconvexity/models/scenarios.py +58 -88
  25. pyconvexity/solvers/__init__.py +5 -5
  26. pyconvexity/solvers/pypsa/__init__.py +3 -3
  27. pyconvexity/solvers/pypsa/api.py +150 -134
  28. pyconvexity/solvers/pypsa/batch_loader.py +165 -162
  29. pyconvexity/solvers/pypsa/builder.py +390 -291
  30. pyconvexity/solvers/pypsa/constraints.py +184 -162
  31. pyconvexity/solvers/pypsa/solver.py +968 -663
  32. pyconvexity/solvers/pypsa/storage.py +1377 -671
  33. pyconvexity/timeseries.py +63 -60
  34. pyconvexity/validation/__init__.py +14 -6
  35. pyconvexity/validation/rules.py +95 -84
  36. pyconvexity-0.4.1.dist-info/METADATA +46 -0
  37. pyconvexity-0.4.1.dist-info/RECORD +42 -0
  38. pyconvexity/data/schema/04_scenario_schema.sql +0 -122
  39. pyconvexity/data/schema/migrate_add_geometries.sql +0 -73
  40. pyconvexity-0.4.0.dist-info/METADATA +0 -138
  41. pyconvexity-0.4.0.dist-info/RECORD +0 -44
  42. {pyconvexity-0.4.0.dist-info → pyconvexity-0.4.1.dist-info}/WHEEL +0 -0
  43. {pyconvexity-0.4.0.dist-info → pyconvexity-0.4.1.dist-info}/top_level.txt +0 -0
@@ -18,6 +18,7 @@ logger = logging.getLogger(__name__)
18
18
  @dataclass
19
19
  class SolveResults:
20
20
  """Represents solve results for a scenario."""
21
+
21
22
  network_statistics: Dict[str, Any]
22
23
  metadata: Dict[str, Any]
23
24
  status: str
@@ -28,58 +29,65 @@ class SolveResults:
28
29
  @dataclass
29
30
  class YearlyResults:
30
31
  """Represents yearly solve results."""
32
+
31
33
  year: int
32
34
  network_statistics: Dict[str, Any]
33
35
  metadata: Dict[str, Any]
34
36
 
35
37
 
36
38
  def get_solve_results(
37
- conn: sqlite3.Connection,
38
- network_id: int,
39
- scenario_id: Optional[int] = None
39
+ conn: sqlite3.Connection, scenario_id: Optional[int] = None
40
40
  ) -> Optional[SolveResults]:
41
41
  """
42
- Get overall solve results for a scenario.
43
-
42
+ Get overall solve results for a scenario (single network per database).
43
+
44
44
  Args:
45
45
  conn: Database connection
46
- network_id: Network ID
47
- scenario_id: Scenario ID (uses master scenario if None)
48
-
46
+ scenario_id: Scenario ID (NULL for base network)
47
+
49
48
  Returns:
50
49
  SolveResults object or None if no results found
51
50
  """
52
- # Resolve scenario ID if not provided
51
+ # Query based on scenario_id (NULL for base network)
53
52
  if scenario_id is None:
54
- from pyconvexity.models.scenarios import get_master_scenario
55
- scenario = get_master_scenario(conn, network_id)
56
- scenario_id = scenario.id
57
-
58
- cursor = conn.execute("""
59
- SELECT results_json, metadata_json, solve_status, objective_value, solve_time_seconds
60
- FROM network_solve_results
61
- WHERE network_id = ? AND scenario_id = ?
62
- ORDER BY solved_at DESC
63
- LIMIT 1
64
- """, (network_id, scenario_id))
65
-
53
+ cursor = conn.execute(
54
+ """
55
+ SELECT results_json, metadata_json, solve_status, objective_value, solve_time_seconds
56
+ FROM network_solve_results
57
+ WHERE scenario_id IS NULL
58
+ ORDER BY solved_at DESC
59
+ LIMIT 1
60
+ """
61
+ )
62
+ else:
63
+ cursor = conn.execute(
64
+ """
65
+ SELECT results_json, metadata_json, solve_status, objective_value, solve_time_seconds
66
+ FROM network_solve_results
67
+ WHERE scenario_id = ?
68
+ ORDER BY solved_at DESC
69
+ LIMIT 1
70
+ """,
71
+ (scenario_id,),
72
+ )
73
+
66
74
  row = cursor.fetchone()
67
75
  if not row:
68
76
  return None
69
-
77
+
70
78
  try:
71
79
  results_json = json.loads(row[0]) if row[0] else {}
72
80
  metadata_json = json.loads(row[1]) if row[1] else {}
73
-
81
+
74
82
  # Extract network_statistics from results_json
75
- network_statistics = results_json.get('network_statistics', {})
76
-
83
+ network_statistics = results_json.get("network_statistics", {})
84
+
77
85
  return SolveResults(
78
86
  network_statistics=network_statistics,
79
87
  metadata=metadata_json,
80
- status=row[2] or 'unknown',
88
+ status=row[2] or "unknown",
81
89
  objective_value=row[3],
82
- solve_time=row[4] or 0.0
90
+ solve_time=row[4] or 0.0,
83
91
  )
84
92
  except json.JSONDecodeError as e:
85
93
  logger.error(f"Error parsing JSON for scenario {scenario_id}: {e}")
@@ -87,52 +95,54 @@ def get_solve_results(
87
95
 
88
96
 
89
97
  def get_yearly_results(
90
- conn: sqlite3.Connection,
91
- network_id: int,
92
- scenario_id: Optional[int] = None
98
+ conn: sqlite3.Connection, scenario_id: Optional[int] = None
93
99
  ) -> Dict[int, YearlyResults]:
94
100
  """
95
- Get year-by-year solve results for a scenario.
96
-
101
+ Get year-by-year solve results for a scenario (single network per database).
102
+
97
103
  Args:
98
104
  conn: Database connection
99
- network_id: Network ID
100
- scenario_id: Scenario ID (uses master scenario if None)
101
-
105
+ scenario_id: Scenario ID (NULL for base network)
106
+
102
107
  Returns:
103
108
  Dictionary mapping years to YearlyResults objects
104
109
  """
105
- # Resolve scenario ID if not provided
110
+ # Query based on scenario_id (NULL for base network)
106
111
  if scenario_id is None:
107
- from pyconvexity.models.scenarios import get_master_scenario
108
- scenario = get_master_scenario(conn, network_id)
109
- scenario_id = scenario.id
110
-
111
- cursor = conn.execute("""
112
- SELECT year, results_json, metadata_json
113
- FROM network_solve_results_by_year
114
- WHERE network_id = ? AND scenario_id = ?
115
- ORDER BY year
116
- """, (network_id, scenario_id))
117
-
112
+ cursor = conn.execute(
113
+ """
114
+ SELECT year, results_json, metadata_json
115
+ FROM network_solve_results_by_year
116
+ WHERE scenario_id IS NULL
117
+ ORDER BY year
118
+ """
119
+ )
120
+ else:
121
+ cursor = conn.execute(
122
+ """
123
+ SELECT year, results_json, metadata_json
124
+ FROM network_solve_results_by_year
125
+ WHERE scenario_id = ?
126
+ ORDER BY year
127
+ """,
128
+ (scenario_id,),
129
+ )
130
+
118
131
  yearly_results = {}
119
132
  for row in cursor.fetchall():
120
133
  year = row[0]
121
134
  try:
122
135
  results_json = json.loads(row[1]) if row[1] else {}
123
136
  metadata_json = json.loads(row[2]) if row[2] else {}
124
-
137
+
125
138
  # Extract network_statistics from results_json
126
- network_statistics = results_json.get('network_statistics', {})
127
-
139
+ network_statistics = results_json.get("network_statistics", {})
140
+
128
141
  yearly_results[year] = YearlyResults(
129
- year=year,
130
- network_statistics=network_statistics,
131
- metadata=metadata_json
142
+ year=year, network_statistics=network_statistics, metadata=metadata_json
132
143
  )
133
144
  except json.JSONDecodeError as e:
134
145
  logger.error(f"Error parsing JSON for year {year}: {e}")
135
146
  continue
136
-
137
- return yearly_results
138
147
 
148
+ return yearly_results
@@ -16,145 +16,115 @@ logger = logging.getLogger(__name__)
16
16
 
17
17
  @dataclass
18
18
  class Scenario:
19
- """Represents a scenario in the network."""
19
+ """Represents a scenario (single network per database)."""
20
+
20
21
  id: int
21
- network_id: int
22
22
  name: str
23
23
  description: Optional[str]
24
- is_master: bool
24
+ probability: Optional[float] # For stochastic optimization
25
25
  created_at: str
26
26
 
27
27
 
28
- def list_scenarios(conn: sqlite3.Connection, network_id: int) -> List[Scenario]:
28
+ def list_scenarios(conn: sqlite3.Connection) -> List[Scenario]:
29
29
  """
30
- List all scenarios for a network.
31
-
30
+ List all scenarios (single network per database).
31
+
32
32
  Args:
33
33
  conn: Database connection
34
- network_id: Network ID
35
-
34
+
36
35
  Returns:
37
- List of Scenario objects ordered by master first, then by creation date
36
+ List of Scenario objects ordered by creation date
38
37
  """
39
- cursor = conn.execute("""
40
- SELECT id, network_id, name, description, is_master, created_at
38
+ cursor = conn.execute(
39
+ """
40
+ SELECT id, name, description, probability, created_at
41
41
  FROM scenarios
42
- WHERE network_id = ?
43
- ORDER BY is_master DESC, created_at
44
- """, (network_id,))
45
-
42
+ ORDER BY created_at
43
+ """
44
+ )
45
+
46
46
  scenarios = []
47
47
  for row in cursor.fetchall():
48
- scenarios.append(Scenario(
49
- id=row[0],
50
- network_id=row[1],
51
- name=row[2],
52
- description=row[3],
53
- is_master=bool(row[4]),
54
- created_at=row[5]
55
- ))
56
-
48
+ scenarios.append(
49
+ Scenario(
50
+ id=row[0],
51
+ name=row[1],
52
+ description=row[2],
53
+ probability=row[3],
54
+ created_at=row[4],
55
+ )
56
+ )
57
+
57
58
  return scenarios
58
59
 
59
60
 
60
- def get_scenario_by_name(conn: sqlite3.Connection, network_id: int, name: str) -> Scenario:
61
+ def get_scenario_by_name(conn: sqlite3.Connection, name: str) -> Scenario:
61
62
  """
62
- Get a scenario by name.
63
-
63
+ Get a scenario by name (single network per database).
64
+
64
65
  Args:
65
66
  conn: Database connection
66
- network_id: Network ID
67
67
  name: Scenario name
68
-
68
+
69
69
  Returns:
70
70
  Scenario object
71
-
71
+
72
72
  Raises:
73
73
  ValidationError: If scenario doesn't exist
74
74
  """
75
- cursor = conn.execute("""
76
- SELECT id, network_id, name, description, is_master, created_at
75
+ cursor = conn.execute(
76
+ """
77
+ SELECT id, name, description, probability, created_at
77
78
  FROM scenarios
78
- WHERE network_id = ? AND name = ?
79
- """, (network_id, name))
80
-
79
+ WHERE name = ?
80
+ """,
81
+ (name,),
82
+ )
83
+
81
84
  row = cursor.fetchone()
82
85
  if not row:
83
- raise ValidationError(f"Scenario '{name}' not found for network {network_id}")
84
-
86
+ raise ValidationError(f"Scenario '{name}' not found")
87
+
85
88
  return Scenario(
86
89
  id=row[0],
87
- network_id=row[1],
88
- name=row[2],
89
- description=row[3],
90
- is_master=bool(row[4]),
91
- created_at=row[5]
90
+ name=row[1],
91
+ description=row[2],
92
+ probability=row[3],
93
+ created_at=row[4],
92
94
  )
93
95
 
94
96
 
95
97
  def get_scenario_by_id(conn: sqlite3.Connection, scenario_id: int) -> Scenario:
96
98
  """
97
99
  Get a scenario by ID.
98
-
100
+
99
101
  Args:
100
102
  conn: Database connection
101
103
  scenario_id: Scenario ID
102
-
104
+
103
105
  Returns:
104
106
  Scenario object
105
-
107
+
106
108
  Raises:
107
109
  ValidationError: If scenario doesn't exist
108
110
  """
109
- cursor = conn.execute("""
110
- SELECT id, network_id, name, description, is_master, created_at
111
+ cursor = conn.execute(
112
+ """
113
+ SELECT id, name, description, probability, created_at
111
114
  FROM scenarios
112
115
  WHERE id = ?
113
- """, (scenario_id,))
114
-
115
- row = cursor.fetchone()
116
- if not row:
117
- raise ValidationError(f"Scenario with ID {scenario_id} not found")
118
-
119
- return Scenario(
120
- id=row[0],
121
- network_id=row[1],
122
- name=row[2],
123
- description=row[3],
124
- is_master=bool(row[4]),
125
- created_at=row[5]
116
+ """,
117
+ (scenario_id,),
126
118
  )
127
119
 
128
-
129
- def get_master_scenario(conn: sqlite3.Connection, network_id: int) -> Scenario:
130
- """
131
- Get the master scenario for a network.
132
-
133
- Args:
134
- conn: Database connection
135
- network_id: Network ID
136
-
137
- Returns:
138
- Scenario object for the master scenario
139
-
140
- Raises:
141
- ValidationError: If master scenario doesn't exist
142
- """
143
- cursor = conn.execute("""
144
- SELECT id, network_id, name, description, is_master, created_at
145
- FROM scenarios
146
- WHERE network_id = ? AND is_master = TRUE
147
- """, (network_id,))
148
-
149
120
  row = cursor.fetchone()
150
121
  if not row:
151
- raise ValidationError(f"No master scenario found for network {network_id}")
152
-
122
+ raise ValidationError(f"Scenario with ID {scenario_id} not found")
123
+
153
124
  return Scenario(
154
125
  id=row[0],
155
- network_id=row[1],
156
- name=row[2],
157
- description=row[3],
158
- is_master=bool(row[4]),
159
- created_at=row[5]
126
+ name=row[1],
127
+ description=row[2],
128
+ probability=row[3],
129
+ created_at=row[4],
160
130
  )
@@ -12,18 +12,18 @@ try:
12
12
  solve_pypsa_network,
13
13
  load_network_components,
14
14
  apply_constraints,
15
- store_solve_results
15
+ store_solve_results,
16
16
  )
17
-
17
+
18
18
  __all__ = [
19
19
  "solve_network",
20
- "build_pypsa_network",
20
+ "build_pypsa_network",
21
21
  "solve_pypsa_network",
22
22
  "load_network_components",
23
23
  "apply_constraints",
24
- "store_solve_results"
24
+ "store_solve_results",
25
25
  ]
26
-
26
+
27
27
  except ImportError:
28
28
  # PyPSA not available
29
29
  __all__ = []
@@ -11,14 +11,14 @@ from pyconvexity.solvers.pypsa.api import (
11
11
  solve_pypsa_network,
12
12
  load_network_components,
13
13
  apply_constraints,
14
- store_solve_results
14
+ store_solve_results,
15
15
  )
16
16
 
17
17
  __all__ = [
18
18
  "solve_network",
19
19
  "build_pypsa_network",
20
- "solve_pypsa_network",
20
+ "solve_pypsa_network",
21
21
  "load_network_components",
22
22
  "apply_constraints",
23
- "store_solve_results"
23
+ "store_solve_results",
24
24
  ]