gridconnect 0.2.0__tar.gz → 0.2.2__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.
- gridconnect-0.2.2/LICENSE +21 -0
- {gridconnect-0.2.0 → gridconnect-0.2.2}/PKG-INFO +3 -2
- {gridconnect-0.2.0 → gridconnect-0.2.2}/README.md +1 -1
- gridconnect-0.2.2/gridconnect/io/__init__.py +6 -0
- gridconnect-0.2.2/gridconnect/io/profiles.py +116 -0
- {gridconnect-0.2.0 → gridconnect-0.2.2}/gridconnect/matching/data.py +30 -21
- {gridconnect-0.2.0 → gridconnect-0.2.2}/gridconnect/network/loads.py +34 -36
- {gridconnect-0.2.0 → gridconnect-0.2.2}/gridconnect.egg-info/PKG-INFO +3 -2
- {gridconnect-0.2.0 → gridconnect-0.2.2}/gridconnect.egg-info/SOURCES.txt +1 -0
- {gridconnect-0.2.0 → gridconnect-0.2.2}/pyproject.toml +4 -2
- gridconnect-0.2.0/LICENSE +0 -25
- gridconnect-0.2.0/gridconnect/io/__init__.py +0 -5
- {gridconnect-0.2.0 → gridconnect-0.2.2}/gridconnect/analysis/__init__.py +0 -0
- {gridconnect-0.2.0 → gridconnect-0.2.2}/gridconnect/analysis/load_shifting.py +0 -0
- {gridconnect-0.2.0 → gridconnect-0.2.2}/gridconnect/analysis/power_flow.py +0 -0
- {gridconnect-0.2.0 → gridconnect-0.2.2}/gridconnect/analysis/power_proxy.py +0 -0
- {gridconnect-0.2.0 → gridconnect-0.2.2}/gridconnect/analysis/reconfiguration.py +0 -0
- {gridconnect-0.2.0 → gridconnect-0.2.2}/gridconnect/analysis/reinforcement.py +0 -0
- {gridconnect-0.2.0 → gridconnect-0.2.2}/gridconnect/api/__init__.py +0 -0
- {gridconnect-0.2.0 → gridconnect-0.2.2}/gridconnect/api/building_bus_matcher.py +0 -0
- {gridconnect-0.2.0 → gridconnect-0.2.2}/gridconnect/api/manager.py +0 -0
- {gridconnect-0.2.0 → gridconnect-0.2.2}/gridconnect/io/conversion.py +0 -0
- {gridconnect-0.2.0 → gridconnect-0.2.2}/gridconnect/matching/__init__.py +0 -0
- {gridconnect-0.2.0 → gridconnect-0.2.2}/gridconnect/matching/area.py +0 -0
- {gridconnect-0.2.0 → gridconnect-0.2.2}/gridconnect/matching/buses.py +0 -0
- {gridconnect-0.2.0 → gridconnect-0.2.2}/gridconnect/matching/config.py +0 -0
- {gridconnect-0.2.0 → gridconnect-0.2.2}/gridconnect/matching/network.py +0 -0
- {gridconnect-0.2.0 → gridconnect-0.2.2}/gridconnect/matching/splitting.py +0 -0
- {gridconnect-0.2.0 → gridconnect-0.2.2}/gridconnect/network/__init__.py +0 -0
- {gridconnect-0.2.0 → gridconnect-0.2.2}/gridconnect/network/snapshots.py +0 -0
- {gridconnect-0.2.0 → gridconnect-0.2.2}/gridconnect/network/state.py +0 -0
- {gridconnect-0.2.0 → gridconnect-0.2.2}/gridconnect/network/topology.py +0 -0
- {gridconnect-0.2.0 → gridconnect-0.2.2}/gridconnect/plotting/__init__.py +0 -0
- {gridconnect-0.2.0 → gridconnect-0.2.2}/gridconnect/plotting/building_bus_match.py +0 -0
- {gridconnect-0.2.0 → gridconnect-0.2.2}/gridconnect/plotting/network.py +0 -0
- {gridconnect-0.2.0 → gridconnect-0.2.2}/gridconnect/results/__init__.py +0 -0
- {gridconnect-0.2.0 → gridconnect-0.2.2}/gridconnect/results/constraints.py +0 -0
- {gridconnect-0.2.0 → gridconnect-0.2.2}/gridconnect/results/loads.py +0 -0
- {gridconnect-0.2.0 → gridconnect-0.2.2}/gridconnect/results/reinforcement.py +0 -0
- {gridconnect-0.2.0 → gridconnect-0.2.2}/gridconnect/results/snapshots.py +0 -0
- {gridconnect-0.2.0 → gridconnect-0.2.2}/gridconnect/utils/logger.py +0 -0
- {gridconnect-0.2.0 → gridconnect-0.2.2}/gridconnect.egg-info/dependency_links.txt +0 -0
- {gridconnect-0.2.0 → gridconnect-0.2.2}/gridconnect.egg-info/requires.txt +0 -0
- {gridconnect-0.2.0 → gridconnect-0.2.2}/gridconnect.egg-info/top_level.txt +0 -0
- {gridconnect-0.2.0 → gridconnect-0.2.2}/setup.cfg +0 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Yoann Chiche
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: gridconnect
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.2
|
|
4
4
|
Summary: A package for connecting building-scale load profiles to distribution networks and preparing simulation-ready network models.
|
|
5
5
|
Author-email: Yoann Chiche <yoann.chiche@minesparis.psl.eu>
|
|
6
|
+
License-Expression: MIT
|
|
6
7
|
Requires-Python: >=3.12
|
|
7
8
|
Description-Content-Type: text/markdown
|
|
8
9
|
License-File: LICENSE
|
|
@@ -105,7 +106,7 @@ data. Default paths are configurable through `BuildingBusMatchConfig` and
|
|
|
105
106
|
| Input | Format | Required content |
|
|
106
107
|
| --- | --- | --- |
|
|
107
108
|
| Building data | District Parquet files | Building identifiers, geometry, usage, and floor area. |
|
|
108
|
-
| Load profiles | `energy_model_<district>.parquet` |
|
|
109
|
+
| Load profiles | `energy_model_static_<district>.parquet` and `energy_model_hourly_<district>.parquet` (`building_eload` >= 0.6.0), or a single legacy `energy_model_<district>.parquet` | Static: `building_id`, `name`, `main_usage`, `district`, `total_electricity_need`, and `floor_area`. Hourly: `building_id`, `datetime`, and `electricity_need` (kW). A legacy file holds all of these columns. |
|
|
109
110
|
| Building-to-bus connections | Parquet or DataFrame | The connection schema returned by `BuildingBusMatcher`. |
|
|
110
111
|
| District areas | Parquet | District geometry used to resolve the feeder study area. |
|
|
111
112
|
| ORE network geometries | Parquet | Low- and medium-voltage overhead and underground line geometries. |
|
|
@@ -77,7 +77,7 @@ data. Default paths are configurable through `BuildingBusMatchConfig` and
|
|
|
77
77
|
| Input | Format | Required content |
|
|
78
78
|
| --- | --- | --- |
|
|
79
79
|
| Building data | District Parquet files | Building identifiers, geometry, usage, and floor area. |
|
|
80
|
-
| Load profiles | `energy_model_<district>.parquet` |
|
|
80
|
+
| Load profiles | `energy_model_static_<district>.parquet` and `energy_model_hourly_<district>.parquet` (`building_eload` >= 0.6.0), or a single legacy `energy_model_<district>.parquet` | Static: `building_id`, `name`, `main_usage`, `district`, `total_electricity_need`, and `floor_area`. Hourly: `building_id`, `datetime`, and `electricity_need` (kW). A legacy file holds all of these columns. |
|
|
81
81
|
| Building-to-bus connections | Parquet or DataFrame | The connection schema returned by `BuildingBusMatcher`. |
|
|
82
82
|
| District areas | Parquet | District geometry used to resolve the feeder study area. |
|
|
83
83
|
| ORE network geometries | Parquet | Low- and medium-voltage overhead and underground line geometries. |
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
"""Input and output conversion helpers for GridConnect datasets.
|
|
2
|
+
|
|
3
|
+
The package provides conversion of serialized-geometry CSV input to GeoParquet
|
|
4
|
+
for the building-to-bus matching workflow, and reading of per-district building
|
|
5
|
+
energy profiles in both ``building_eload`` output layouts.
|
|
6
|
+
"""
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
"""Locate and read per-district building energy profiles.
|
|
2
|
+
|
|
3
|
+
``building_eload`` >= 0.6.0 writes each district's output as separate
|
|
4
|
+
``energy_model_static_<district>.parquet`` (one row per building) and
|
|
5
|
+
``energy_model_hourly_<district>.parquet`` (one row per building and hour)
|
|
6
|
+
files. Earlier versions wrote a single denormalized
|
|
7
|
+
``energy_model_<district>.parquet`` file. Both layouts are supported; the split
|
|
8
|
+
layout takes precedence when both are present.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
import logging
|
|
14
|
+
from pathlib import Path
|
|
15
|
+
|
|
16
|
+
import polars as pl
|
|
17
|
+
|
|
18
|
+
STATIC_COLUMNS = (
|
|
19
|
+
"building_id",
|
|
20
|
+
"name",
|
|
21
|
+
"main_usage",
|
|
22
|
+
"district",
|
|
23
|
+
"total_electricity_need",
|
|
24
|
+
"floor_area",
|
|
25
|
+
)
|
|
26
|
+
HOURLY_COLUMNS = ("building_id", "datetime", "electricity_need")
|
|
27
|
+
|
|
28
|
+
logger = logging.getLogger(__name__)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def static_profile_path(profile_dir: str | Path, district: str) -> Path:
|
|
32
|
+
"""Return the split-layout static profile path of one district."""
|
|
33
|
+
return Path(profile_dir) / f"energy_model_static_{district}.parquet"
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def hourly_profile_path(profile_dir: str | Path, district: str) -> Path:
|
|
37
|
+
"""Return the split-layout hourly profile path of one district."""
|
|
38
|
+
return Path(profile_dir) / f"energy_model_hourly_{district}.parquet"
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def legacy_profile_path(profile_dir: str | Path, district: str) -> Path:
|
|
42
|
+
"""Return the single-file (pre-0.6.0) profile path of one district."""
|
|
43
|
+
return Path(profile_dir) / f"energy_model_{district}.parquet"
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def has_district_profiles(profile_dir: str | Path, district: str) -> bool:
|
|
47
|
+
"""Return whether a directory holds profiles of one district in any layout."""
|
|
48
|
+
return (
|
|
49
|
+
static_profile_path(profile_dir, district).exists()
|
|
50
|
+
and hourly_profile_path(profile_dir, district).exists()
|
|
51
|
+
) or legacy_profile_path(profile_dir, district).exists()
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def scan_district_profiles(
|
|
55
|
+
profile_dir: str | Path, district: str
|
|
56
|
+
) -> pl.LazyFrame | None:
|
|
57
|
+
"""Lazily read one district's hourly profiles with per-building attributes.
|
|
58
|
+
|
|
59
|
+
Args:
|
|
60
|
+
profile_dir: Directory containing district profile files.
|
|
61
|
+
district: District identifier.
|
|
62
|
+
|
|
63
|
+
Returns:
|
|
64
|
+
Lazy frame with at least the ``HOURLY_COLUMNS`` and ``STATIC_COLUMNS``
|
|
65
|
+
columns, or ``None`` when no profile file exists for the district.
|
|
66
|
+
``electricity_need`` is passed through in kW.
|
|
67
|
+
|
|
68
|
+
Raises:
|
|
69
|
+
ValueError: If a split-layout file lacks a required column.
|
|
70
|
+
"""
|
|
71
|
+
static_file = static_profile_path(profile_dir, district)
|
|
72
|
+
hourly_file = hourly_profile_path(profile_dir, district)
|
|
73
|
+
if static_file.exists() and hourly_file.exists():
|
|
74
|
+
return _scan_split_profiles(static_file, hourly_file)
|
|
75
|
+
|
|
76
|
+
legacy_file = legacy_profile_path(profile_dir, district)
|
|
77
|
+
if legacy_file.exists():
|
|
78
|
+
return pl.scan_parquet(legacy_file)
|
|
79
|
+
|
|
80
|
+
if static_file.exists() or hourly_file.exists():
|
|
81
|
+
logger.warning(
|
|
82
|
+
"Incomplete split profiles for district %s in '%s': both '%s' and "
|
|
83
|
+
"'%s' are required.",
|
|
84
|
+
district,
|
|
85
|
+
profile_dir,
|
|
86
|
+
static_file.name,
|
|
87
|
+
hourly_file.name,
|
|
88
|
+
)
|
|
89
|
+
return None
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def _scan_split_profiles(static_file: Path, hourly_file: Path) -> pl.LazyFrame:
|
|
93
|
+
"""Join split-layout hourly rows to their static building attributes."""
|
|
94
|
+
static = pl.scan_parquet(static_file)
|
|
95
|
+
hourly = pl.scan_parquet(hourly_file)
|
|
96
|
+
for frame, columns, file in (
|
|
97
|
+
(static, STATIC_COLUMNS, static_file),
|
|
98
|
+
(hourly, HOURLY_COLUMNS, hourly_file),
|
|
99
|
+
):
|
|
100
|
+
missing = sorted(set(columns).difference(frame.collect_schema().names()))
|
|
101
|
+
if missing:
|
|
102
|
+
raise ValueError(
|
|
103
|
+
f"'{file}' is missing required column(s): {', '.join(missing)}."
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
# Keep the join one-to-many even if several rows share a building_id.
|
|
107
|
+
static = static.select(STATIC_COLUMNS).unique(
|
|
108
|
+
subset="building_id", keep="first", maintain_order=True
|
|
109
|
+
)
|
|
110
|
+
return (
|
|
111
|
+
hourly.select(HOURLY_COLUMNS)
|
|
112
|
+
.join(static, on="building_id", how="inner")
|
|
113
|
+
# building_eload stores district as Categorical; downstream merges
|
|
114
|
+
# compare it against plain strings.
|
|
115
|
+
.with_columns(pl.col("district").cast(pl.String))
|
|
116
|
+
)
|
|
@@ -18,10 +18,13 @@ except ImportError: # pragma: no cover - optional runtime dependency
|
|
|
18
18
|
import geopandas as gpd
|
|
19
19
|
import numpy as np
|
|
20
20
|
import pandas as pd
|
|
21
|
+
import polars as pl
|
|
21
22
|
from shapely import wkb
|
|
22
23
|
from shapely.geometry import Point
|
|
23
24
|
from tqdm import tqdm
|
|
24
25
|
|
|
26
|
+
from gridconnect.io.profiles import scan_district_profiles
|
|
27
|
+
|
|
25
28
|
KW_TO_MW = 1000.0
|
|
26
29
|
REACTIVE_POWER_FACTOR = 0.3
|
|
27
30
|
|
|
@@ -284,13 +287,6 @@ class MatchingDataLoader:
|
|
|
284
287
|
def _load_energy_profiles(self) -> pd.DataFrame:
|
|
285
288
|
"""Load and aggregate building energy profiles."""
|
|
286
289
|
aggregated_frames: list[pd.DataFrame] = []
|
|
287
|
-
columns = [
|
|
288
|
-
"name",
|
|
289
|
-
"building_id",
|
|
290
|
-
"total_electricity_need",
|
|
291
|
-
"electricity_need",
|
|
292
|
-
"floor_area",
|
|
293
|
-
]
|
|
294
290
|
for district_id in tqdm(
|
|
295
291
|
self.district_ids,
|
|
296
292
|
desc="Loading energy profiles",
|
|
@@ -298,24 +294,32 @@ class MatchingDataLoader:
|
|
|
298
294
|
colour="green",
|
|
299
295
|
disable=not logger.isEnabledFor(logging.DEBUG),
|
|
300
296
|
):
|
|
301
|
-
|
|
302
|
-
if
|
|
297
|
+
profiles = scan_district_profiles(self.profiles_dir, district_id)
|
|
298
|
+
if profiles is None:
|
|
303
299
|
logger.warning(
|
|
304
|
-
"Energy profile file not found
|
|
300
|
+
"Energy profile file(s) not found in '%s' (district %s).",
|
|
301
|
+
self.profiles_dir,
|
|
305
302
|
district_id,
|
|
306
|
-
file_path,
|
|
307
303
|
)
|
|
308
304
|
continue
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
305
|
+
aggregated = (
|
|
306
|
+
profiles.with_columns(
|
|
307
|
+
pl.col(
|
|
308
|
+
"total_electricity_need", "electricity_need", "floor_area"
|
|
309
|
+
).cast(pl.Float64)
|
|
310
|
+
)
|
|
311
|
+
.group_by(["name", "building_id"])
|
|
312
|
+
.agg(
|
|
313
|
+
annual_energy_mwh=pl.col("total_electricity_need").first(),
|
|
314
|
+
peak_active_power_mw=pl.col("electricity_need").max(),
|
|
315
|
+
floor_area=pl.col("floor_area").first(),
|
|
316
|
+
)
|
|
317
|
+
.sort(["name", "building_id"])
|
|
318
|
+
.collect()
|
|
319
|
+
.to_pandas()
|
|
318
320
|
)
|
|
321
|
+
if aggregated.empty:
|
|
322
|
+
continue
|
|
319
323
|
aggregated["annual_energy_mwh"] /= KW_TO_MW
|
|
320
324
|
aggregated["peak_active_power_mw"] /= KW_TO_MW
|
|
321
325
|
aggregated["peak_reactive_power_mvar"] = (
|
|
@@ -418,7 +422,12 @@ class MatchingDataLoader:
|
|
|
418
422
|
crs=self.metric_crs,
|
|
419
423
|
)
|
|
420
424
|
|
|
421
|
-
|
|
425
|
+
# DuckDB guarantees no row order without ORDER BY, and bus ids below are
|
|
426
|
+
# positional: sort on row content so ids are stable across runs.
|
|
427
|
+
df["geometry"] = df["geometry"].map(bytes)
|
|
428
|
+
sort_columns = ["geometry"] + (["TENSION"] if "TENSION" in df.columns else [])
|
|
429
|
+
df = df.sort_values(sort_columns, kind="mergesort").reset_index(drop=True)
|
|
430
|
+
df["geometry"] = df["geometry"].apply(wkb.loads)
|
|
422
431
|
df["geometry"] = df["geometry"].apply(
|
|
423
432
|
lambda geom: (
|
|
424
433
|
Point(geom.coords[-1])
|
|
@@ -17,6 +17,7 @@ import polars as pl
|
|
|
17
17
|
from tqdm import tqdm
|
|
18
18
|
|
|
19
19
|
from gridconnect.api.building_bus_matcher import CONNECTIONS_COLUMNS
|
|
20
|
+
from gridconnect.io.profiles import has_district_profiles, scan_district_profiles
|
|
20
21
|
from gridconnect.results.loads import LoadProfiles
|
|
21
22
|
|
|
22
23
|
CONNECTION_MATCH_KEYS = (
|
|
@@ -313,15 +314,16 @@ class NetworkLoadBuilder:
|
|
|
313
314
|
unit="district",
|
|
314
315
|
disable=not VERBOSE,
|
|
315
316
|
):
|
|
316
|
-
|
|
317
|
-
if not
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
317
|
+
district_dir = profile_dir
|
|
318
|
+
if not has_district_profiles(district_dir, district):
|
|
319
|
+
district_dir = profile_dir / f"{self.network.name}"
|
|
320
|
+
profiles = scan_district_profiles(district_dir, district)
|
|
321
|
+
if profiles is None:
|
|
322
|
+
logger.warning(
|
|
323
|
+
"Profile file(s) not found for district %s in '%s'.",
|
|
324
|
+
district,
|
|
325
|
+
district_dir,
|
|
322
326
|
)
|
|
323
|
-
if not file.exists():
|
|
324
|
-
logger.warning("Profile file not found: %s.", file)
|
|
325
327
|
continue
|
|
326
328
|
|
|
327
329
|
district_buildings = (
|
|
@@ -332,12 +334,12 @@ class NetworkLoadBuilder:
|
|
|
332
334
|
.drop_duplicates()
|
|
333
335
|
.rename(columns={"cleabs": "name"})
|
|
334
336
|
)
|
|
335
|
-
dfs.append(self._import_profile(
|
|
337
|
+
dfs.append(self._import_profile(profiles, district_buildings))
|
|
336
338
|
|
|
337
339
|
if not dfs:
|
|
338
340
|
return pd.DataFrame(columns=LOAD_COLUMNS)
|
|
339
341
|
logger.debug("Concatenating profile(s) read from files.")
|
|
340
|
-
return pl.concat(dfs).to_pandas()
|
|
342
|
+
return pl.concat(dfs, how="vertical_relaxed").to_pandas()
|
|
341
343
|
|
|
342
344
|
def _load_profiles_from_frame(
|
|
343
345
|
self,
|
|
@@ -379,36 +381,32 @@ class NetworkLoadBuilder:
|
|
|
379
381
|
)
|
|
380
382
|
return filtered
|
|
381
383
|
|
|
382
|
-
def _import_profile(
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
building_pairs = (
|
|
389
|
-
pl.from_pandas(building_pairs)
|
|
390
|
-
.lazy()
|
|
391
|
-
.with_columns(pl.col("building_id").cast(pl.Int64))
|
|
392
|
-
)
|
|
384
|
+
def _import_profile(
|
|
385
|
+
self, profiles: pl.LazyFrame, building_pairs: pd.DataFrame
|
|
386
|
+
) -> pl.DataFrame:
|
|
387
|
+
"""Filter one district's profiles on exact (name, building_id) pairs."""
|
|
388
|
+
if building_pairs.empty:
|
|
389
|
+
return pl.DataFrame(schema=LOAD_COLUMNS)
|
|
393
390
|
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
.collect()
|
|
400
|
-
).with_columns(
|
|
401
|
-
electricity_need=pl.col("electricity_need") / KW_TO_MW,
|
|
402
|
-
)
|
|
391
|
+
building_pairs = (
|
|
392
|
+
pl.from_pandas(building_pairs)
|
|
393
|
+
.lazy()
|
|
394
|
+
.with_columns(pl.col("building_id").cast(pl.Int64))
|
|
395
|
+
)
|
|
403
396
|
|
|
404
|
-
|
|
397
|
+
df = (
|
|
398
|
+
profiles.with_columns(pl.col("building_id").cast(pl.Int64))
|
|
399
|
+
.join(building_pairs, on=["name", "building_id"], how="inner")
|
|
400
|
+
.select(LOAD_COLUMNS)
|
|
401
|
+
.collect()
|
|
402
|
+
).with_columns(
|
|
403
|
+
electricity_need=pl.col("electricity_need").cast(pl.Float64) / KW_TO_MW,
|
|
404
|
+
)
|
|
405
405
|
|
|
406
|
-
|
|
407
|
-
return df
|
|
406
|
+
unique_buildings = len(df["building_id"].unique())
|
|
408
407
|
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
return pd.DataFrame(columns=LOAD_COLUMNS)
|
|
408
|
+
logger.debug(f"Loaded {unique_buildings} profile(s).")
|
|
409
|
+
return df
|
|
412
410
|
|
|
413
411
|
def format_profiles(
|
|
414
412
|
self,
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: gridconnect
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.2
|
|
4
4
|
Summary: A package for connecting building-scale load profiles to distribution networks and preparing simulation-ready network models.
|
|
5
5
|
Author-email: Yoann Chiche <yoann.chiche@minesparis.psl.eu>
|
|
6
|
+
License-Expression: MIT
|
|
6
7
|
Requires-Python: >=3.12
|
|
7
8
|
Description-Content-Type: text/markdown
|
|
8
9
|
License-File: LICENSE
|
|
@@ -105,7 +106,7 @@ data. Default paths are configurable through `BuildingBusMatchConfig` and
|
|
|
105
106
|
| Input | Format | Required content |
|
|
106
107
|
| --- | --- | --- |
|
|
107
108
|
| Building data | District Parquet files | Building identifiers, geometry, usage, and floor area. |
|
|
108
|
-
| Load profiles | `energy_model_<district>.parquet` |
|
|
109
|
+
| Load profiles | `energy_model_static_<district>.parquet` and `energy_model_hourly_<district>.parquet` (`building_eload` >= 0.6.0), or a single legacy `energy_model_<district>.parquet` | Static: `building_id`, `name`, `main_usage`, `district`, `total_electricity_need`, and `floor_area`. Hourly: `building_id`, `datetime`, and `electricity_need` (kW). A legacy file holds all of these columns. |
|
|
109
110
|
| Building-to-bus connections | Parquet or DataFrame | The connection schema returned by `BuildingBusMatcher`. |
|
|
110
111
|
| District areas | Parquet | District geometry used to resolve the feeder study area. |
|
|
111
112
|
| ORE network geometries | Parquet | Low- and medium-voltage overhead and underground line geometries. |
|
|
@@ -17,6 +17,7 @@ gridconnect/api/building_bus_matcher.py
|
|
|
17
17
|
gridconnect/api/manager.py
|
|
18
18
|
gridconnect/io/__init__.py
|
|
19
19
|
gridconnect/io/conversion.py
|
|
20
|
+
gridconnect/io/profiles.py
|
|
20
21
|
gridconnect/matching/__init__.py
|
|
21
22
|
gridconnect/matching/area.py
|
|
22
23
|
gridconnect/matching/buses.py
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
[build-system]
|
|
2
|
-
requires = ["setuptools>=
|
|
2
|
+
requires = ["setuptools>=77", "wheel"]
|
|
3
3
|
build-backend = "setuptools.build_meta"
|
|
4
4
|
|
|
5
5
|
[tool.setuptools.packages.find]
|
|
@@ -8,7 +8,7 @@ include = ["gridconnect*"]
|
|
|
8
8
|
[project]
|
|
9
9
|
name = "gridconnect"
|
|
10
10
|
|
|
11
|
-
version = "0.2.
|
|
11
|
+
version = "0.2.2"
|
|
12
12
|
description = "A package for connecting building-scale load profiles to distribution networks and preparing simulation-ready network models."
|
|
13
13
|
|
|
14
14
|
authors = [
|
|
@@ -17,6 +17,8 @@ authors = [
|
|
|
17
17
|
|
|
18
18
|
readme = "README.md"
|
|
19
19
|
requires-python = ">=3.12"
|
|
20
|
+
license = "MIT"
|
|
21
|
+
license-files = ["LICENSE"]
|
|
20
22
|
|
|
21
23
|
dependencies = [
|
|
22
24
|
"networkx",
|
gridconnect-0.2.0/LICENSE
DELETED
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
The MIT License (MIT)
|
|
2
|
-
=====================
|
|
3
|
-
|
|
4
|
-
- Copyright © `2026` `Yoann CHICHE`
|
|
5
|
-
|
|
6
|
-
Permission is hereby granted, free of charge, to any person
|
|
7
|
-
obtaining a copy of this software and associated documentation
|
|
8
|
-
files (the “Software”), to deal in the Software without
|
|
9
|
-
restriction, including without limitation the rights to use,
|
|
10
|
-
copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
11
|
-
copies of the Software, and to permit persons to whom the
|
|
12
|
-
Software is furnished to do so, subject to the following
|
|
13
|
-
conditions:
|
|
14
|
-
|
|
15
|
-
The above copyright notice and this permission notice shall be
|
|
16
|
-
included in all copies or substantial portions of the Software.
|
|
17
|
-
|
|
18
|
-
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND,
|
|
19
|
-
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
|
20
|
-
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
21
|
-
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
|
22
|
-
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
|
23
|
-
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
24
|
-
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
|
25
|
-
OTHER DEALINGS IN THE SOFTWARE.
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|