domainiac 1.0.0__py3-none-any.whl → 2.0.0__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.
domainiac/__init__.py CHANGED
@@ -1 +1,8 @@
1
- from .managers import MeteringManager, PlantManager, ResourceManager, UnitManager
1
+ from .managers import (
2
+ MeteringManager,
3
+ NWPManager,
4
+ PlantManager,
5
+ ResourceManager,
6
+ UnitManager,
7
+ )
8
+ from .modeling import Coordinate, Group, Neighborhood, NWPParameter, NWPProvider, Plant
@@ -1,5 +1,6 @@
1
1
  from .masterdata_manager import MasterdataManager
2
2
  from .metering_manager import MeteringManager
3
+ from .nwp_manager import NWPManager
3
4
  from .plant_manager import PlantManager
4
5
  from .resource_manager import ResourceManager
5
6
  from .unit_manager import UnitManager
@@ -59,7 +59,10 @@ class MasterdataManager:
59
59
 
60
60
  # Apply the filters
61
61
  for column, value in filters.items():
62
- df = df[df[column] == value].reset_index()
62
+ if isinstance(value, list):
63
+ df = df[df[column].isin(value)].reset_index()
64
+ else:
65
+ df = df[df[column] == value].reset_index()
63
66
 
64
67
  for column in columns:
65
68
  if column not in df.columns:
@@ -0,0 +1,298 @@
1
+ import datamazing.pandas as pdz
2
+ import numpy as np
3
+ import pandas as pd
4
+ from sklearn.neighbors import KDTree
5
+
6
+ from ..modeling.nwp import Coordinate, Neighborhood, NWPParameter, NWPProvider
7
+
8
+
9
+ class NWPManager:
10
+ """
11
+ Contains all logic concerning NWP data.
12
+ This includes:
13
+ - Getting NWP data
14
+ - Getting average NWP data
15
+ - Finding closest NWP coordinates
16
+ """
17
+
18
+ NWP_INTERPOLATION_METHODS = {
19
+ # Wind and temperature are estimates
20
+ # of the instantaneous value at time t.
21
+ # We use linear interpolation.
22
+ NWPParameter.WIND: ("linear", "instantanous"),
23
+ NWPParameter.TEMPERATURE: ("linear", "instantanous"),
24
+ # solar is an estimate of the average value
25
+ # in the interval [t-1, t].
26
+ # We use PCHIP interpolation to ensure
27
+ # a smooth shape and non-negative values
28
+ # (https://docs.scipy.org/doc/scipy/reference/generated/scipy.interpolate.PchipInterpolator.html)
29
+ NWPParameter.SOLAR: ("pchip", "interval_average"),
30
+ }
31
+
32
+ NWP_RESOLUTIONS = {
33
+ (NWPProvider.ECMWF, NWPParameter.WIND): pd.Timedelta("PT3H"),
34
+ (NWPProvider.ECMWF, NWPParameter.TEMPERATURE): pd.Timedelta("PT3H"),
35
+ (NWPProvider.ECMWF, NWPParameter.SOLAR): pd.Timedelta("PT1H"),
36
+ (NWPProvider.DMI, NWPParameter.WIND): pd.Timedelta("PT1H"),
37
+ (NWPProvider.DMI, NWPParameter.TEMPERATURE): pd.Timedelta("PT1H"),
38
+ (NWPProvider.DMI, NWPParameter.SOLAR): pd.Timedelta("PT1H"),
39
+ (NWPProvider.CONWX, NWPParameter.WIND): pd.Timedelta("PT1H"),
40
+ (NWPProvider.CONWX, NWPParameter.TEMPERATURE): pd.Timedelta("PT1H"),
41
+ (NWPProvider.CONWX, NWPParameter.SOLAR): pd.Timedelta("PT1H"),
42
+ }
43
+
44
+ def __init__(
45
+ self,
46
+ db: pdz.Database,
47
+ time_interval: pdz.TimeInterval,
48
+ resolution: pd.Timedelta,
49
+ ):
50
+ self.db = db
51
+ self.time_interval = time_interval
52
+ self.resolution = resolution
53
+
54
+ self._nwp_coordinates_kd_tree = dict()
55
+ self._nwp_table_name_prefix = "forecastNwp"
56
+
57
+ @classmethod
58
+ def get_nwp_resolution(
59
+ cls, provider: NWPProvider, parameter: NWPParameter
60
+ ) -> pd.Timedelta:
61
+ """
62
+ Get the resolution of the NWP parameter.
63
+ """
64
+ return cls.NWP_RESOLUTIONS[(provider, parameter)]
65
+
66
+ @classmethod
67
+ def get_nwp_interpolation_method(cls, parameter: NWPParameter) -> tuple[str, str]:
68
+ """
69
+ Get the interpolation method and temporal aggregation for the NWP parameter.
70
+ """
71
+ return cls.NWP_INTERPOLATION_METHODS[parameter]
72
+
73
+ @staticmethod
74
+ def calculate_wind_speed_from_vectors(u: pd.Series, v: pd.Series) -> pd.Series:
75
+ return np.sqrt(u**2 + v**2)
76
+
77
+ def _get_table_name(
78
+ self,
79
+ provider: NWPProvider,
80
+ parameter: NWPParameter,
81
+ ) -> str:
82
+ """
83
+ Get the table name for a given NWP parameter.
84
+ """
85
+ return self._nwp_table_name_prefix + provider.value + parameter.value
86
+
87
+ def _get_kd_tree(self, provider: NWPProvider, parameter: NWPParameter) -> KDTree:
88
+ """
89
+ Create a KDTree from the coordinates from the nwp parameter table.
90
+ This is done lazily, so that the KDTree is only created once.
91
+ """
92
+ key = (provider, parameter)
93
+ if key not in self._nwp_coordinates_kd_tree:
94
+ # Base KDTree on one hour of data
95
+ query_time_interval = pdz.TimeInterval(
96
+ self.time_interval.left, self.time_interval.left + pd.Timedelta("PT1H")
97
+ )
98
+
99
+ df = self.db.query(
100
+ table_name=self._get_table_name(provider, parameter),
101
+ time_interval=query_time_interval,
102
+ )
103
+
104
+ self._nwp_coordinates_kd_tree[key] = dict()
105
+
106
+ # Make KDTree for altitude
107
+ self._nwp_coordinates_kd_tree[key]["altitude"] = KDTree(
108
+ df[["altitude_m"]].drop_duplicates()
109
+ )
110
+
111
+ # Make KDTree for latitude and longitude
112
+ self._nwp_coordinates_kd_tree[key]["plane"] = KDTree(
113
+ df[["latitude", "longitude"]].drop_duplicates()
114
+ )
115
+
116
+ return self._nwp_coordinates_kd_tree[key]
117
+
118
+ def get_nwp_neighbors(
119
+ self,
120
+ provider: NWPProvider,
121
+ parameter: NWPParameter,
122
+ neighborhood: Neighborhood,
123
+ ) -> list[Coordinate]:
124
+ """
125
+ Find n closest coordinates based on the NWP parameter table.
126
+
127
+ Note: This assumes that coordinates does not change over time.
128
+ """
129
+ kd_tree = self._get_kd_tree(provider, parameter)
130
+
131
+ altitude_indices = kd_tree["altitude"].query(
132
+ np.c_[neighborhood.coordinate.altitude],
133
+ k=1,
134
+ return_distance=False,
135
+ )
136
+ # TODO: Allow this to handle multiple altitudes
137
+ nearest_altitude = kd_tree["altitude"].data.base[altitude_indices][0][0][0]
138
+
139
+ plane_indices = kd_tree["plane"].query(
140
+ np.c_[neighborhood.coordinate.latitude, neighborhood.coordinate.longitude],
141
+ k=neighborhood.num_neighbors,
142
+ return_distance=False,
143
+ )
144
+ nearest_planes = kd_tree["plane"].data.base[plane_indices][0]
145
+
146
+ coordinates = [
147
+ Coordinate(latitude=plane[0], longitude=plane[1], altitude=nearest_altitude)
148
+ for plane in nearest_planes
149
+ ]
150
+
151
+ return coordinates
152
+
153
+ def _get_nwp_parameter(
154
+ self,
155
+ provider: NWPProvider,
156
+ parameter: NWPParameter,
157
+ coordinate: Coordinate,
158
+ ) -> pd.DataFrame:
159
+ """
160
+ Get NWP data for a given parameter, coordinate and altitude.
161
+ """
162
+ table = self._get_table_name(provider, parameter)
163
+
164
+ filters = {
165
+ "latitude": coordinate.latitude,
166
+ "longitude": coordinate.longitude,
167
+ "altitude_m": coordinate.altitude,
168
+ }
169
+
170
+ # Pad time interval with one resolution
171
+ # This is needed for the interpolation to work as we shift back in time
172
+ nwp_resolution = self.get_nwp_resolution(provider, parameter)
173
+
174
+ # Pad time interval to make sure interpolation is possible
175
+ padded_time_interval = pdz.TimeInterval(
176
+ self.time_interval.left - (nwp_resolution / 2),
177
+ self.time_interval.right + (nwp_resolution * 1.5),
178
+ )
179
+
180
+ df = self.db.query(table, padded_time_interval, filters=filters)
181
+
182
+ df = df.drop(
183
+ columns=[
184
+ "created_time_utc",
185
+ "altitude_m",
186
+ "longitude",
187
+ "latitude",
188
+ ]
189
+ )
190
+
191
+ return df
192
+
193
+ def _get_interpolated_nwp_parameter(
194
+ self,
195
+ provider: NWPProvider,
196
+ parameter: NWPParameter,
197
+ coordinate: Coordinate,
198
+ ):
199
+ """
200
+ Get NWP data for a given parameter, coordinate and altitude,
201
+ using the proper interpolation technique.
202
+ """
203
+ df = self._get_nwp_parameter(provider, parameter, coordinate)
204
+
205
+ nwp_resolution = self.get_nwp_resolution(provider, parameter)
206
+ interpolation_method, temporal_aggregation = self.get_nwp_interpolation_method(
207
+ parameter
208
+ )
209
+
210
+ match temporal_aggregation:
211
+ # if the parameter is an interval average,
212
+ # it makes best sense to interpolate from
213
+ # midpoint to midpoint. Otherwise, we just
214
+ # interpolate regularly.
215
+ case "interval_average":
216
+ shift = -1 / 2
217
+ case "instantanous":
218
+ shift = 0
219
+
220
+ df_shifted = pdz.shift_time(df, on="time_utc", period=shift * nwp_resolution)
221
+
222
+ # Interpolate
223
+ df_resampled = pdz.resample(
224
+ df_shifted, on="time_utc", resolution=self.resolution / 2
225
+ ).interpolate(interpolation_method)
226
+
227
+ # Shift back time, so that the output time series again represents
228
+ # the value by the endpoint.
229
+ df_re_shifted = pdz.shift_time(
230
+ df_resampled, on="time_utc", period=-shift * self.resolution
231
+ )
232
+
233
+ # Make sure times is divisible by the resolution
234
+ df_resolution = df_re_shifted[
235
+ df_re_shifted["time_utc"].dt.round(self.resolution)
236
+ == df_re_shifted["time_utc"]
237
+ ]
238
+
239
+ return df_resolution
240
+
241
+ def get_nwp_parameter(
242
+ self,
243
+ provider: NWPProvider,
244
+ parameter: NWPParameter,
245
+ coordinate: Coordinate,
246
+ ):
247
+ df_nwp = self._get_interpolated_nwp_parameter(provider, parameter, coordinate)
248
+
249
+ # Ensure data is within time_interval defined by user
250
+ df_nwp = df_nwp.query(
251
+ f"time_utc >= '{self.time_interval.left}' "
252
+ f"and time_utc <= '{self.time_interval.right}'",
253
+ )
254
+
255
+ return df_nwp
256
+
257
+ def get_nwp_parameter_in_neighborhood(
258
+ self,
259
+ provider: NWPProvider,
260
+ parameter: NWPParameter,
261
+ neighborhood: Neighborhood,
262
+ ) -> pd.DataFrame:
263
+ """
264
+ Get average NWP data for a given parameter, coordinates and altitude.
265
+ """
266
+ dfs = []
267
+
268
+ neighbors = self.get_nwp_neighbors(provider, parameter, neighborhood)
269
+ for coordinate in neighbors:
270
+ df_coordinate = self.get_nwp_parameter(provider, parameter, coordinate)
271
+ dfs.append(df_coordinate)
272
+
273
+ df = pd.concat(dfs)
274
+ df = pdz.group(df, "time_utc").agg("mean")
275
+
276
+ return df
277
+
278
+ def get_nwp(
279
+ self,
280
+ provider: NWPProvider,
281
+ parameters: list[NWPParameter],
282
+ neighborhood: Neighborhood,
283
+ ) -> pd.DataFrame:
284
+ """
285
+ Get weather features for a given coordinate.
286
+ """
287
+ dfs = []
288
+ for parameter in parameters:
289
+ df_param = self.get_nwp_parameter_in_neighborhood(
290
+ provider,
291
+ parameter,
292
+ neighborhood,
293
+ )
294
+ dfs.append(df_param)
295
+
296
+ df = pdz.merge_many(dfs=dfs, on=["time_utc"])
297
+
298
+ return df
@@ -24,7 +24,13 @@ class PlantManager(MasterdataManager):
24
24
  def get_plants(
25
25
  self,
26
26
  filters: dict = {},
27
- columns: list = [
27
+ columns: list | None = None,
28
+ ) -> pd.DataFrame:
29
+ """Gets the plants for a given plant type.
30
+ Filters for plants valid at the end of time interval.
31
+ Filters by default for plants in operation.
32
+ """
33
+ default_columns = [
28
34
  "plant_id",
29
35
  "masterdata_gsrn",
30
36
  "datahub_gsrn_e18",
@@ -33,12 +39,11 @@ class PlantManager(MasterdataManager):
33
39
  "is_tso_connected",
34
40
  "valid_from_date_utc",
35
41
  "valid_to_date_utc",
36
- ],
37
- ) -> pd.DataFrame:
38
- """Gets the plants for a given plant type.
39
- Filters for plants valid at the end of time interval.
40
- Filters by default for plants in operation.
41
- """
42
+ ]
43
+ if not columns:
44
+ columns = default_columns
45
+ else:
46
+ columns = list(set(default_columns + columns))
42
47
  return self.get_data("masterdataPlant", filters=filters, columns=columns)
43
48
 
44
49
  def get_installed_power_timeseries(self, gsrn: str) -> pd.DataFrame:
@@ -62,3 +67,37 @@ class PlantManager(MasterdataManager):
62
67
  return df_plant.filter(["time_utc", "installed_power_MW"]).reset_index(
63
68
  drop=True
64
69
  )
70
+
71
+ def _get_corrected_installed_power(
72
+ self, gsrn: str, df_invalid_periods: pd.DataFrame
73
+ ):
74
+ df_times = self.time_interval.to_range(self.resolution).to_frame(
75
+ index=False, name="time_utc"
76
+ )
77
+ df = self.get_installed_power_timeseries(gsrn=gsrn)
78
+
79
+ # explode invalid periods to time series
80
+ df_invalid_periods = df_invalid_periods.query(f"masterdata_gsrn == '{gsrn}'")
81
+ df_invalid_periods = pdz.merge(
82
+ df_times,
83
+ df_invalid_periods,
84
+ left_time="time_utc",
85
+ right_period=("start_date_utc", "end_date_utc"),
86
+ )
87
+
88
+ df = pdz.merge(
89
+ df,
90
+ df_invalid_periods,
91
+ on="time_utc",
92
+ how="left",
93
+ )
94
+
95
+ # correct installed power for invalid periods
96
+ df["installed_power_MW"] = df["installed_power_MW"].where(
97
+ df["corrected_installed_power_MW"].isnull(),
98
+ df["corrected_installed_power_MW"],
99
+ )
100
+
101
+ df = df[["time_utc", "installed_power_MW"]]
102
+
103
+ return df
@@ -24,18 +24,19 @@ class UnitManager(MasterdataManager):
24
24
  def get_units(
25
25
  self,
26
26
  filters: dict = {},
27
- columns: list = [
28
- "masterdata_gsrn",
29
- "installed_power_MW",
30
- "plant_id",
31
- "price_area",
32
- "valid_from_date_utc",
33
- "valid_to_date_utc",
34
- "power_system_resource_type",
35
- ],
27
+ columns: list | None = None,
36
28
  ) -> pd.DataFrame:
37
29
  """Gets the units for a given unit type.
38
30
  Filters for units valid at the end of time interval.
39
31
  Filters by default for units in operation.
40
32
  """
33
+ default_columns = [
34
+ "masterdata_gsrn",
35
+ "plant_id",
36
+ "power_system_resource_type",
37
+ ]
38
+ if not columns:
39
+ columns = default_columns
40
+ else:
41
+ columns = list(set(default_columns + columns))
41
42
  return self.get_data("masterdataUnit", filters=filters, columns=columns)
@@ -0,0 +1,2 @@
1
+ from .nwp import Coordinate, Neighborhood, NWPParameter, NWPProvider
2
+ from .plant import Group, Plant
@@ -0,0 +1,27 @@
1
+ from dataclasses import dataclass
2
+ from enum import Enum
3
+
4
+
5
+ class NWPParameter(Enum):
6
+ SOLAR = "Solar"
7
+ WIND = "Wind"
8
+ TEMPERATURE = "Temperature"
9
+
10
+
11
+ class NWPProvider(Enum):
12
+ ECMWF = "Ecmwf"
13
+ CONWX = "Conwx"
14
+ DMI = "Dmi"
15
+
16
+
17
+ @dataclass(frozen=True)
18
+ class Coordinate:
19
+ latitude: float
20
+ longitude: float
21
+ altitude: float
22
+
23
+
24
+ @dataclass(frozen=True)
25
+ class Neighborhood:
26
+ coordinate: Coordinate
27
+ num_neighbors: int
@@ -0,0 +1,58 @@
1
+ from dataclasses import dataclass
2
+
3
+ import pandas as pd
4
+
5
+ from .nwp import Coordinate
6
+
7
+
8
+ @dataclass(frozen=True)
9
+ class Plant:
10
+ plant_gsrn: str
11
+ plant_name: str
12
+ datahub_gsrn_e18: str
13
+ price_area: str
14
+ coordinate: Coordinate
15
+ installed_power_MW: float
16
+
17
+ @classmethod
18
+ def plants_from_df(cls, df: pd.DataFrame) -> list["Plant"]:
19
+ plants = []
20
+ for _, row in df.iterrows():
21
+ plant = cls(
22
+ plant_gsrn=row["plant_gsrn"],
23
+ plant_name=row["plant_name"],
24
+ datahub_gsrn_e18=row["datahub_gsrn_e18"],
25
+ price_area=row["price_area"],
26
+ coordinate=Coordinate(
27
+ latitude=row["latitude"],
28
+ longitude=row["longitude"],
29
+ altitude=0,
30
+ ),
31
+ installed_power_MW=row["installed_power_MW"],
32
+ )
33
+ plants.append(plant)
34
+ return plants
35
+
36
+
37
+ @dataclass(frozen=True)
38
+ class Group:
39
+ coordinate: Coordinate
40
+ installed_power_MW: float
41
+ identifiers: dict[str, str]
42
+
43
+ @classmethod
44
+ def groups_from_df(cls, df: pd.DataFrame, identifiers: list[str]) -> list["Group"]:
45
+ groups = []
46
+ for _, row in df.iterrows():
47
+ identifiers = {identifier: row[identifier] for identifier in identifiers}
48
+ group = cls(
49
+ identifiers=identifiers,
50
+ installed_power_MW=row["installed_power_MW"],
51
+ coordinate=Coordinate(
52
+ latitude=row["latitude"],
53
+ longitude=row["longitude"],
54
+ altitude=0,
55
+ ),
56
+ )
57
+ groups.append(group)
58
+ return groups
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.3
2
2
  Name: domainiac
3
- Version: 1.0.0
3
+ Version: 2.0.0
4
4
  Summary: Package for working with Energinet data, but with specialized functions used for Enigma.
5
5
  Author: Team Enigma
6
6
  Author-email: gridop-enigma@energinet.dk
@@ -9,4 +9,5 @@ Classifier: Programming Language :: Python :: 3
9
9
  Classifier: Programming Language :: Python :: 3.10
10
10
  Requires-Dist: datamazing (>=5.0.2,<6.0.0)
11
11
  Requires-Dist: pandas (>=2.2.0,<3.0.0)
12
+ Requires-Dist: scikit-learn (>=1.3.0,<2.0.0)
12
13
  Requires-Dist: typeguard (>=4.2.1,<5.0.0)
@@ -0,0 +1,16 @@
1
+ domainiac/__init__.py,sha256=Wuix4jscZD4sy7YB0RAedAo_eiRW5AcPlef58lKvg6s,207
2
+ domainiac/managers/__init__.py,sha256=x7NZxFAuJig2TZGJ71niwMyPyDvxjaiE647BJQU0qD8,256
3
+ domainiac/managers/masterdata_manager.py,sha256=H-Dk-dvZZ3bqgV66Xf1V4GQnQWprzLqSKdfD1MPLQgo,2223
4
+ domainiac/managers/metering_manager.py,sha256=NXGHuNjWiAUE08fGIPAfvwbzlKESbKaO5uyxLNyZKC0,995
5
+ domainiac/managers/nwp_manager.py,sha256=qGr4c1ZbUnVRmhX_LmjaobBKN6BBXUn5bbYVKa_5lo4,9921
6
+ domainiac/managers/plant_manager.py,sha256=o0HkOs1nf1uri7OMMH-VgPTAONWjuEE80SyD9RDf0n8,3186
7
+ domainiac/managers/resource_manager.py,sha256=QMkNcrgyXLLlkCR9kg4-nFjTaYHtlsldex-b_8lAmO0,3730
8
+ domainiac/managers/unit_manager.py,sha256=DYF-AiBHzUGZJE5XlTLcszvgIQkSzZs_s9o35l4bkVk,1219
9
+ domainiac/modeling/__init__.py,sha256=Ac5feYZ9gVLayU1Cb1ACqOyDrmvcxoOvZuUkOeXVKf8,101
10
+ domainiac/modeling/nwp.py,sha256=PolrBdQn8W-e1M0_pefvmLn2mr4HT0NqlYlkyCV0dds,438
11
+ domainiac/modeling/plant.py,sha256=48KEcfiMk90fOAzOeI5qtf1FcGvpyBNIRmN2zvx9CLc,1699
12
+ domainiac/wrappers/__init__.py,sha256=vZOw9maXgVoAvudZqioD-GTPkgPI6fm7_CUELQcR_-g,43
13
+ domainiac/wrappers/cache_wrapper.py,sha256=jDg-Lt_y-YzItyP-74tGULOxb07s_CcutmOHP1Ie00Q,355
14
+ domainiac-2.0.0.dist-info/METADATA,sha256=O0KM7oPIYCtDKihwlHr8leFJ7XErIsjb8WMobGibAic,510
15
+ domainiac-2.0.0.dist-info/WHEEL,sha256=IYZQI976HJqqOpQU6PHkJ8fb3tMNBFjg-Cn-pwAbaFM,88
16
+ domainiac-2.0.0.dist-info/RECORD,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: poetry-core 1.9.0
2
+ Generator: poetry-core 2.0.1
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
@@ -1,12 +0,0 @@
1
- domainiac/__init__.py,sha256=7-KoUot9OZgzMoOLhuHInlvyt9EfL5QjuZagTbAJksU,82
2
- domainiac/managers/__init__.py,sha256=QzkZNv8_3G0TPcfQia45RWBnASYR6WIuY6jlFBeley0,220
3
- domainiac/managers/masterdata_manager.py,sha256=tl37saXdH1aeek3Oo58nTxl-eL2txKnmpwVHkyij7n0,2099
4
- domainiac/managers/metering_manager.py,sha256=NXGHuNjWiAUE08fGIPAfvwbzlKESbKaO5uyxLNyZKC0,995
5
- domainiac/managers/plant_manager.py,sha256=sEVc3_RaJaG-beT7kv3rgvCtaWLCuXuhxy21mk360Og,1944
6
- domainiac/managers/resource_manager.py,sha256=QMkNcrgyXLLlkCR9kg4-nFjTaYHtlsldex-b_8lAmO0,3730
7
- domainiac/managers/unit_manager.py,sha256=8B0hRu_tlZfaZYKDnM0NAQoJ6a1s_8DicBM9v6GEkYE,1174
8
- domainiac/wrappers/__init__.py,sha256=vZOw9maXgVoAvudZqioD-GTPkgPI6fm7_CUELQcR_-g,43
9
- domainiac/wrappers/cache_wrapper.py,sha256=jDg-Lt_y-YzItyP-74tGULOxb07s_CcutmOHP1Ie00Q,355
10
- domainiac-1.0.0.dist-info/METADATA,sha256=LWQrY1F34IUdCbZbsXqQ8Di6DOMVidN7bq1deCbzUFU,465
11
- domainiac-1.0.0.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
12
- domainiac-1.0.0.dist-info/RECORD,,