tyba-client 0.4.13__py3-none-any.whl → 0.4.16__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 tyba-client might be problematic. Click here for more details.
- tyba_client/client.py +405 -41
- tyba_client/forecast.py +90 -0
- tyba_client/io.py +7 -2
- tyba_client/operations.py +61 -1
- tyba_client/solar_resource.py +6 -0
- {tyba_client-0.4.13.dist-info → tyba_client-0.4.16.dist-info}/METADATA +3 -2
- tyba_client-0.4.16.dist-info/RECORD +12 -0
- {tyba_client-0.4.13.dist-info → tyba_client-0.4.16.dist-info}/WHEEL +1 -1
- tyba_client-0.4.13.dist-info/LICENSE +0 -21
- tyba_client-0.4.13.dist-info/RECORD +0 -13
- /LICENSE → /tyba_client-0.4.16.dist-info/LICENSE +0 -0
tyba_client/client.py
CHANGED
|
@@ -2,9 +2,15 @@ import pandas as pd
|
|
|
2
2
|
import typing as t
|
|
3
3
|
from requests import Response
|
|
4
4
|
|
|
5
|
-
from tyba_client.models import GenerationModel, PVStorageModel, StandaloneStorageModel
|
|
6
5
|
from tyba_client.forecast import Forecast
|
|
7
|
-
from generation_models import JobModel
|
|
6
|
+
from generation_models import JobModel, GenerationModel, PVStorageModel, StandaloneStorageModel
|
|
7
|
+
|
|
8
|
+
from generation_models.v0_output_schema import (
|
|
9
|
+
GenerationModelResults,
|
|
10
|
+
PVStorageModelResults,
|
|
11
|
+
StandaloneStorageModelSimpleResults,
|
|
12
|
+
StandaloneStorageModelWithDownstreamResults
|
|
13
|
+
)
|
|
8
14
|
import json
|
|
9
15
|
import requests
|
|
10
16
|
import time
|
|
@@ -12,25 +18,104 @@ from structlog import get_logger
|
|
|
12
18
|
from typing import Callable
|
|
13
19
|
|
|
14
20
|
from tyba_client.operations import Operations
|
|
21
|
+
from pydantic import BaseModel, Field
|
|
22
|
+
from enum import Enum
|
|
15
23
|
|
|
16
24
|
logger = get_logger()
|
|
17
25
|
|
|
18
26
|
|
|
19
|
-
|
|
20
|
-
|
|
27
|
+
V0Results = t.Union[
|
|
28
|
+
GenerationModelResults,
|
|
29
|
+
PVStorageModelResults,
|
|
30
|
+
StandaloneStorageModelSimpleResults,
|
|
31
|
+
StandaloneStorageModelWithDownstreamResults
|
|
32
|
+
]
|
|
33
|
+
|
|
34
|
+
class Market(str, Enum):
|
|
35
|
+
"""Indicator for which market to pull pricing data for"""
|
|
36
|
+
RT = "realtime"
|
|
37
|
+
"""Indicates pricing data for the Real Time (RT) Market is desired"""
|
|
38
|
+
DA = "dayahead"
|
|
39
|
+
"""Indicates pricing data for the Day Ahead (DA) Market is desired"""
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class AncillaryService(str, Enum):
|
|
43
|
+
"""Indicator for which service to pull pricing data for"""
|
|
44
|
+
REGULATION_UP = "Regulation Up"
|
|
45
|
+
"""Indicates pricing data for the Regulation Up service is desired"""
|
|
46
|
+
REGULATION_DOWN = "Regulation Down"
|
|
47
|
+
"""Indicates pricing data for the Regulation Down service is desired"""
|
|
48
|
+
RESERVES = "Reserves"
|
|
49
|
+
"""Indicates pricing data for the Reserves service is desired"""
|
|
50
|
+
ECRS = "ECRS"
|
|
51
|
+
"""Indicates pricing data for the ERCOT Contingency Reserve Service is desired"""
|
|
21
52
|
|
|
22
|
-
|
|
53
|
+
class Ancillary(object):
|
|
54
|
+
"""Interface for accessing Tyba's historical *ancillary* price data"""
|
|
55
|
+
def __init__(self, services: 'Services'):
|
|
23
56
|
self.services = services
|
|
24
57
|
|
|
25
58
|
def get(self, route, params=None):
|
|
26
59
|
return self.services.get(f"ancillary/{route}", params=params)
|
|
27
60
|
|
|
28
|
-
def get_pricing_regions(self, *, iso, service, market):
|
|
29
|
-
"""
|
|
61
|
+
def get_pricing_regions(self, *, iso: str, service: AncillaryService, market: Market) -> Response:
|
|
62
|
+
"""Get the name and available year ranges for all ancillary service pricing regions that meet the ISO,
|
|
63
|
+
service and market criteria.
|
|
64
|
+
|
|
65
|
+
:param iso: ISO name. Possible values can be found by calling :meth:`Services.get_all_isos`
|
|
66
|
+
:param service: specifies which ancillary service to pull prices for
|
|
67
|
+
:param market: specifies whether to pull day ahead or real time prices for the given service
|
|
68
|
+
:return: :class:`~requests.Response` containing an **array** of JSON objects with schema
|
|
69
|
+
:class:`AncillaryRegionData`. For example:
|
|
70
|
+
|
|
71
|
+
.. code:: python
|
|
72
|
+
|
|
73
|
+
[
|
|
74
|
+
{
|
|
75
|
+
'region': 'Pacific Northwest - SP15',
|
|
76
|
+
'start_year': 2010,
|
|
77
|
+
'end_year': 2025
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
'region': 'WAPA',
|
|
81
|
+
...
|
|
82
|
+
},
|
|
83
|
+
...
|
|
84
|
+
]
|
|
85
|
+
|
|
86
|
+
"""
|
|
30
87
|
return self.get("regions", {"iso": iso, "service": service, "market": market})
|
|
31
88
|
|
|
32
|
-
def get_prices(self, *, iso, service, market, region, start_year, end_year):
|
|
33
|
-
"""
|
|
89
|
+
def get_prices(self, *, iso: str, service: AncillaryService, market: Market, region: str, start_year: int, end_year: int) -> Response:
|
|
90
|
+
"""Get price time series data for a single region/service combination
|
|
91
|
+
|
|
92
|
+
:param iso: ISO name. Possible values can be found by calling :meth:`Services.get_all_isos`
|
|
93
|
+
:param service: specifies which ancillary service to pull prices for
|
|
94
|
+
:param market: specifies whether to pull day ahead or real time prices for the given service
|
|
95
|
+
:param region: specific region within the ISO to pull prices for. Possible values can be found by calling
|
|
96
|
+
:meth:`get_pricing_regions`
|
|
97
|
+
:param start_year: the year prices should start
|
|
98
|
+
:param end_year: the year prices should end
|
|
99
|
+
:return: :class:`~requests.Response` containing a JSON object with schema :class:`PriceTimeSeries`. For example:
|
|
100
|
+
|
|
101
|
+
.. code:: python
|
|
102
|
+
|
|
103
|
+
{
|
|
104
|
+
'prices': [
|
|
105
|
+
61.7929,
|
|
106
|
+
58.1359,
|
|
107
|
+
61.4939,
|
|
108
|
+
....
|
|
109
|
+
],
|
|
110
|
+
'datetimes': [
|
|
111
|
+
'2022-01-01T00:00:00Z',
|
|
112
|
+
'2022-01-01T01:00:00Z',
|
|
113
|
+
'2022-01-01T02:00:00Z',
|
|
114
|
+
....
|
|
115
|
+
],
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
"""
|
|
34
119
|
return self.get(
|
|
35
120
|
"prices",
|
|
36
121
|
{
|
|
@@ -45,9 +130,9 @@ class Ancillary(object):
|
|
|
45
130
|
|
|
46
131
|
|
|
47
132
|
class LMP(object):
|
|
48
|
-
"""
|
|
49
|
-
|
|
50
|
-
def __init__(self, services):
|
|
133
|
+
"""Interface for accessing Tyba's historical *energy* price data
|
|
134
|
+
"""
|
|
135
|
+
def __init__(self, services: 'Services'):
|
|
51
136
|
self.services = services
|
|
52
137
|
self._route_base = "lmp"
|
|
53
138
|
|
|
@@ -57,12 +142,75 @@ class LMP(object):
|
|
|
57
142
|
def post(self, route, json):
|
|
58
143
|
return self.services.post(f"{self._route_base}/{route}", json=json)
|
|
59
144
|
|
|
60
|
-
def get_all_nodes(self, *, iso):
|
|
61
|
-
"""
|
|
145
|
+
def get_all_nodes(self, *, iso: str) -> requests.Response:
|
|
146
|
+
"""Get node names, IDs and other metadata for all nodes within the given ISO territory.
|
|
147
|
+
|
|
148
|
+
:param iso: ISO name. Possible values can be found by calling :meth:`Services.get_all_isos`
|
|
149
|
+
:return: :class:`~requests.Response` containing an **array** of JSON objects with schema
|
|
150
|
+
:class:`NodeData`. For example:
|
|
151
|
+
|
|
152
|
+
.. code:: python
|
|
153
|
+
|
|
154
|
+
[
|
|
155
|
+
{'da_end_year': 2025,
|
|
156
|
+
'rt_end_year': 2025,
|
|
157
|
+
'rt_start_year': 2023,
|
|
158
|
+
'name': 'CLAP_WWRSR1-APND',
|
|
159
|
+
'id': '10017280350',
|
|
160
|
+
'da_start_year': 2023,
|
|
161
|
+
'zone': 'SDGE',
|
|
162
|
+
'type': 'GENERATOR'},
|
|
163
|
+
{'da_start_year': 2015,
|
|
164
|
+
'rt_end_year': 2025,
|
|
165
|
+
'zone': '',
|
|
166
|
+
'name': 'ELCENTRO_2_N001:IVLY2',
|
|
167
|
+
'type': 'SPTIE',
|
|
168
|
+
'substation': '',
|
|
169
|
+
'da_end_year': 2025,
|
|
170
|
+
'id': '10003899356',
|
|
171
|
+
'rt_start_year': 2015},
|
|
172
|
+
...
|
|
173
|
+
]
|
|
174
|
+
|
|
175
|
+
"""
|
|
62
176
|
return self.get("nodes", {"iso": iso})
|
|
63
177
|
|
|
64
|
-
def get_prices(self, *, node_ids, market, start_year, end_year):
|
|
65
|
-
"""
|
|
178
|
+
def get_prices(self, *, node_ids: list[str], market: Market, start_year: int, end_year: int) -> requests.Response:
|
|
179
|
+
"""Get price time series data for a list of node IDs
|
|
180
|
+
|
|
181
|
+
:param node_ids: list of IDs for which prices are desired
|
|
182
|
+
- Maximum length is 8 IDS
|
|
183
|
+
:param market: specifies whether to pull day ahead or real time market prices
|
|
184
|
+
:param start_year: the year prices should start
|
|
185
|
+
:param end_year: the year prices should end
|
|
186
|
+
:return: :class:`~requests.Response` containing a JSON object whose keys are node IDs and whose values
|
|
187
|
+
are objects with schema :class:`PriceTimeSeries`. For example:
|
|
188
|
+
|
|
189
|
+
.. code:: python
|
|
190
|
+
|
|
191
|
+
{
|
|
192
|
+
'10000802793': {
|
|
193
|
+
'prices': [
|
|
194
|
+
61.7929,
|
|
195
|
+
58.1359,
|
|
196
|
+
61.4939,
|
|
197
|
+
....
|
|
198
|
+
],
|
|
199
|
+
'datetimes': [
|
|
200
|
+
'2022-01-01T00:00:00',
|
|
201
|
+
'2022-01-01T01:00:00',
|
|
202
|
+
'2022-01-01T02:00:00',
|
|
203
|
+
....
|
|
204
|
+
],
|
|
205
|
+
...
|
|
206
|
+
},
|
|
207
|
+
'20000004677': {
|
|
208
|
+
...
|
|
209
|
+
},
|
|
210
|
+
...
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
"""
|
|
66
214
|
return self.get(
|
|
67
215
|
"prices",
|
|
68
216
|
{
|
|
@@ -73,7 +221,45 @@ class LMP(object):
|
|
|
73
221
|
},
|
|
74
222
|
)
|
|
75
223
|
|
|
76
|
-
def search_nodes(self, location: t.Optional[str] = None, node_name_filter: t.Optional[str] = None,
|
|
224
|
+
def search_nodes(self, location: t.Optional[str] = None, node_name_filter: t.Optional[str] = None,
|
|
225
|
+
iso_override: t.Optional[str] = None) -> Response:
|
|
226
|
+
"""Get a list of matching nodes based on search criteria. Multiple search criteria (e.g. `location`
|
|
227
|
+
and `node_name_filter`) can be applied in a single request.
|
|
228
|
+
|
|
229
|
+
:param location: location information. There are 3 possible forms:
|
|
230
|
+
|
|
231
|
+
- city/state, e.g. `'dallas, tx'`
|
|
232
|
+
- address, e.g. `'12345 Anywhere Street, Anywhere, TX 12345'`
|
|
233
|
+
- latitude and longitude, e.g. `'29.760427, -95.369804'`
|
|
234
|
+
|
|
235
|
+
:param node_name_filter: partial node name with which to perform a pattern-match, e.g. `'HB_'`
|
|
236
|
+
:param iso_override: ISO signifier, used to constrain search to a single ISO. When equal to ``None``, all ISOs
|
|
237
|
+
are searched based on other criteria. Possible values can be found by calling :meth:`Services.get_all_isos`
|
|
238
|
+
:return: :class:`~requests.Response` containing a JSON object. If matching nodes are found, a `'nodes'` item
|
|
239
|
+
will contain an **array** of objects with schema :class:`NodeSearchData`. If no matching nodes are found,
|
|
240
|
+
an error code will be returned. As an example, a successful search result might look like:
|
|
241
|
+
|
|
242
|
+
.. code:: python
|
|
243
|
+
|
|
244
|
+
{
|
|
245
|
+
"nodes": [
|
|
246
|
+
{
|
|
247
|
+
"node/name": "HB_BUSAVG",
|
|
248
|
+
"node/id": "10000698380",
|
|
249
|
+
"node/iso": "ERCOT",
|
|
250
|
+
"node/lat": 30.850714,
|
|
251
|
+
"node/lng": -97.877628,
|
|
252
|
+
"node/distance-meters": 500.67458
|
|
253
|
+
},
|
|
254
|
+
{
|
|
255
|
+
"node/name": ...,
|
|
256
|
+
...
|
|
257
|
+
},
|
|
258
|
+
...
|
|
259
|
+
]
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
"""
|
|
77
263
|
return self.get(route="search-nodes",
|
|
78
264
|
params={"location": location,
|
|
79
265
|
"node_name_filter": node_name_filter,
|
|
@@ -81,12 +267,15 @@ class LMP(object):
|
|
|
81
267
|
|
|
82
268
|
|
|
83
269
|
class Services(object):
|
|
84
|
-
"""
|
|
270
|
+
"""Interface for accessing Tyba's historical price data
|
|
271
|
+
"""
|
|
272
|
+
def __init__(self, client: 'Client'):
|
|
85
273
|
|
|
86
|
-
def __init__(self, client):
|
|
87
274
|
self.client = client
|
|
88
|
-
self.ancillary = Ancillary(self)
|
|
89
|
-
|
|
275
|
+
self.ancillary: Ancillary = Ancillary(self)
|
|
276
|
+
"""Interface for accessing Tyba's historical *ancillary* price data"""
|
|
277
|
+
self.lmp: LMP = LMP(self)
|
|
278
|
+
"""Interface for accessing Tyba's historical *energy* price data"""
|
|
90
279
|
self._route_base = "services"
|
|
91
280
|
|
|
92
281
|
def get(self, route, params=None):
|
|
@@ -95,30 +284,48 @@ class Services(object):
|
|
|
95
284
|
def post(self, route, json):
|
|
96
285
|
return self.client.post(f"{self._route_base}/{route}", json=json)
|
|
97
286
|
|
|
98
|
-
def get_all_isos(self):
|
|
99
|
-
"""
|
|
287
|
+
def get_all_isos(self) -> requests.Response:
|
|
288
|
+
"""Get of list of all independent system operators and regional transmission operators (generally all referred
|
|
289
|
+
to as ISOs) represented in Tyba's historical price data
|
|
290
|
+
|
|
291
|
+
:return: :class:`~requests.Response` containing JSON array of strings of the available ISO names
|
|
292
|
+
"""
|
|
100
293
|
return self.get("isos")
|
|
101
294
|
|
|
102
295
|
|
|
103
296
|
class Client(object):
|
|
104
|
-
"""
|
|
297
|
+
"""High level interface for interacting with Tyba's API.
|
|
298
|
+
|
|
299
|
+
:param personal_access_token: required for using the python client/API, contact Tyba to obtain
|
|
300
|
+
"""
|
|
105
301
|
|
|
106
302
|
DEFAULT_OPTIONS = {"version": "0.1"}
|
|
107
303
|
|
|
108
304
|
def __init__(
|
|
109
305
|
self,
|
|
110
|
-
personal_access_token,
|
|
111
|
-
host="https://dev.tybaenergy.com",
|
|
112
|
-
request_args=None,
|
|
306
|
+
personal_access_token: str,
|
|
307
|
+
host: str = "https://dev.tybaenergy.com",
|
|
308
|
+
request_args: t.Optional[dict] = None,
|
|
113
309
|
):
|
|
114
|
-
"""A :class:`Client` object for interacting with Tyba's API."""
|
|
115
310
|
self.personal_access_token = personal_access_token
|
|
116
311
|
self.host = host
|
|
117
|
-
self.services = Services(self)
|
|
118
|
-
|
|
312
|
+
self.services: Services = Services(self)
|
|
313
|
+
"""Interface for accessing Tyba's historical price data"""
|
|
314
|
+
self.forecast: Forecast = Forecast(self)
|
|
315
|
+
"""Interface for accessing Tyba's historical price data"""
|
|
119
316
|
self.operations = Operations(self)
|
|
120
317
|
self.request_args = {} if request_args is None else request_args
|
|
121
318
|
|
|
319
|
+
@property
|
|
320
|
+
def ancillary(self) -> Ancillary:
|
|
321
|
+
"""Shortcut to :class:`client.services.ancillary <Ancillary>`"""
|
|
322
|
+
return self.services.ancillary
|
|
323
|
+
|
|
324
|
+
@property
|
|
325
|
+
def lmp(self) -> LMP:
|
|
326
|
+
"""Shortcut to :class:`client.services.lmp <LMP>`"""
|
|
327
|
+
return self.services.lmp
|
|
328
|
+
|
|
122
329
|
def _auth_header(self):
|
|
123
330
|
return self.personal_access_token
|
|
124
331
|
|
|
@@ -141,29 +348,60 @@ class Client(object):
|
|
|
141
348
|
**self.request_args,
|
|
142
349
|
)
|
|
143
350
|
|
|
144
|
-
def schedule_pv(self, pv_model: GenerationModel):
|
|
351
|
+
def schedule_pv(self, pv_model: GenerationModel) -> Response:
|
|
145
352
|
model_json_dict = pv_model.to_dict()
|
|
146
353
|
return self.post("schedule-pv", json=model_json_dict)
|
|
147
354
|
|
|
148
|
-
def schedule_storage(self, storage_model: StandaloneStorageModel):
|
|
355
|
+
def schedule_storage(self, storage_model: StandaloneStorageModel) -> Response:
|
|
149
356
|
model_json_dict = storage_model.to_dict()
|
|
150
357
|
return self.post("schedule-storage", json=model_json_dict)
|
|
151
358
|
|
|
152
|
-
def schedule_pv_storage(self, pv_storage_model: PVStorageModel):
|
|
359
|
+
def schedule_pv_storage(self, pv_storage_model: PVStorageModel) -> Response:
|
|
153
360
|
model_json_dict = pv_storage_model.to_dict()
|
|
154
361
|
return self.post("schedule-pv-storage", json=model_json_dict)
|
|
155
362
|
|
|
156
|
-
def schedule(self, model: JobModel):
|
|
157
|
-
"""
|
|
363
|
+
def schedule(self, model: JobModel) -> Response:
|
|
364
|
+
"""Schedule a model simulation based on the given inputs
|
|
365
|
+
|
|
366
|
+
:param model: a class instance of one of the model classes, e.g.
|
|
367
|
+
:class:`~generation_models.generation_models.StandaloneStorageModel`. Contains all required inputs for
|
|
368
|
+
running a simulation
|
|
369
|
+
:return: :class:`~requests.Response` whose status code indicates whether the model was successfully scheduled.
|
|
370
|
+
If successful, the response will contain a JSON object with an ``'id'`` for the scheduled model run. This id
|
|
371
|
+
can be used with the :meth:`get_status` and :meth:`wait_on_result` endpoints to retrieve status updates and
|
|
372
|
+
model results. The presence of issues can be easily checked by calling the
|
|
373
|
+
:meth:`~requests.Response.raise_for_status` method of the response object. For example:
|
|
374
|
+
|
|
375
|
+
.. code:: python
|
|
376
|
+
|
|
377
|
+
resp = client.schedule(pv)
|
|
378
|
+
resp.raise-for_status() # this will raise an error if the model was not successfully scheduled
|
|
379
|
+
id_ = resp.json()["id"]
|
|
380
|
+
res = client.wait_on_result(id_)
|
|
381
|
+
|
|
382
|
+
"""
|
|
158
383
|
return self.post("schedule-job", json=model.dict())
|
|
159
384
|
|
|
160
385
|
def get_status(self, run_id: str):
|
|
161
|
-
"""
|
|
386
|
+
"""Check the status and retrieve the results of a scheduled model simulation. If a simulation has not
|
|
387
|
+
completed, this endpoint returns the simulation status/progress. If the simulation has completed, it
|
|
388
|
+
returns the model results.
|
|
389
|
+
|
|
390
|
+
:param run_id: ID of the scheduled model simulation
|
|
391
|
+
:return: :class:`~requests.Response` containing a JSON object with schema :class:`ModelStatus`
|
|
392
|
+
"""
|
|
162
393
|
url = "get-status/" + run_id
|
|
163
394
|
return self.get(url)
|
|
164
395
|
|
|
165
396
|
def get_status_v1(self, run_id: str):
|
|
166
|
-
"""
|
|
397
|
+
"""`Deprecated, please use` :meth:`get_status`, `which will support additional results schemas in the near
|
|
398
|
+
future`. Identical to :meth:`get_status`, but returns results for completed simulations in the "V1"
|
|
399
|
+
SLD-style schema
|
|
400
|
+
|
|
401
|
+
:param run_id: ID of the scheduled model simulation
|
|
402
|
+
:return: :class:`~requests.Response` containing a JSON object with schema :class:`ModelStatus` (except with a
|
|
403
|
+
different schema for :attr:`~ModelStatus.result`)
|
|
404
|
+
"""
|
|
167
405
|
return self.get(f"get-status/{run_id}", params={"fmt": "v1"})
|
|
168
406
|
|
|
169
407
|
@staticmethod
|
|
@@ -172,7 +410,7 @@ class Client(object):
|
|
|
172
410
|
wait_time: int,
|
|
173
411
|
log_progress: bool,
|
|
174
412
|
getter: Callable[[str], Response],
|
|
175
|
-
):
|
|
413
|
+
) -> dict:
|
|
176
414
|
while True:
|
|
177
415
|
resp = getter(run_id)
|
|
178
416
|
resp.raise_for_status()
|
|
@@ -190,8 +428,16 @@ class Client(object):
|
|
|
190
428
|
|
|
191
429
|
def wait_on_result(
|
|
192
430
|
self, run_id: str, wait_time: int = 5, log_progress: bool = False
|
|
193
|
-
):
|
|
194
|
-
"""
|
|
431
|
+
) -> dict:
|
|
432
|
+
"""Poll for simulation status and, once complete, return the model results
|
|
433
|
+
|
|
434
|
+
:param run_id: ID of the scheduled model simulation
|
|
435
|
+
:param wait_time: time in seconds to wait between polling/updates
|
|
436
|
+
:param log_progress: indicate whether updates/progress should be logged/displayed. If ``True``, will
|
|
437
|
+
report both :attr:`~ModelStatus.status` and :attr:`~ModelStatus.progress` information
|
|
438
|
+
:return: results dictionary equivalent to :attr:`ModelStatus.result` returned by :meth:`get_status`, with the
|
|
439
|
+
exact schema depending on the model inputs
|
|
440
|
+
"""
|
|
195
441
|
return self._wait_on_result(
|
|
196
442
|
run_id, wait_time, log_progress, getter=self.get_status
|
|
197
443
|
)
|
|
@@ -199,7 +445,16 @@ class Client(object):
|
|
|
199
445
|
def wait_on_result_v1(
|
|
200
446
|
self, run_id: str, wait_time: int = 5, log_progress: bool = False
|
|
201
447
|
):
|
|
202
|
-
"""
|
|
448
|
+
"""`Deprecated, please use :meth:`wait_on_result`, which will support additional results schemas in the near
|
|
449
|
+
future`. Identical to :meth:`wait_on_result`, but returns results for completed simulations in the "V1"
|
|
450
|
+
SLD-style schema
|
|
451
|
+
|
|
452
|
+
:param run_id: ID of the scheduled model simulation
|
|
453
|
+
:param wait_time: time in seconds to wait between polling/updates
|
|
454
|
+
:param log_progress: indicate whether updates/progress should be logged/displayed. If ``True``, will
|
|
455
|
+
report both :attr:`~ModelStatus.status` and :attr:`~ModelStatus.progress` information
|
|
456
|
+
:return: results dictionary with "V1" SLD-style schema
|
|
457
|
+
"""
|
|
203
458
|
res = self._wait_on_result(
|
|
204
459
|
run_id, wait_time, log_progress, getter=self.get_status_v1
|
|
205
460
|
)
|
|
@@ -207,6 +462,7 @@ class Client(object):
|
|
|
207
462
|
|
|
208
463
|
|
|
209
464
|
def parse_v1_result(res: dict):
|
|
465
|
+
""":meta private:"""
|
|
210
466
|
return {
|
|
211
467
|
"hourly": pd.concat(
|
|
212
468
|
{k: pd.DataFrame(v) for k, v in res["hourly"].items()}, axis=1
|
|
@@ -216,4 +472,112 @@ def parse_v1_result(res: dict):
|
|
|
216
472
|
|
|
217
473
|
|
|
218
474
|
class UnknownRunId(ValueError):
|
|
475
|
+
""":meta private:"""
|
|
219
476
|
pass
|
|
477
|
+
|
|
478
|
+
|
|
479
|
+
class NodeType(str, Enum):
|
|
480
|
+
"""Indicator of which type of physical infrastructure is associated with a particular market node"""
|
|
481
|
+
GENERATOR = "GENERATOR"
|
|
482
|
+
"""Not sure"""
|
|
483
|
+
SPTIE = "SPTIE"
|
|
484
|
+
"""Not sure"""
|
|
485
|
+
LOAD = "LOAD"
|
|
486
|
+
"""Not sure"""
|
|
487
|
+
INTERTIE = "INTERTIE"
|
|
488
|
+
"""Not sure"""
|
|
489
|
+
AGGREGATE = "AGGREGATE"
|
|
490
|
+
"""Not sure"""
|
|
491
|
+
HUB = "HUB"
|
|
492
|
+
"""Not sure"""
|
|
493
|
+
NA = "N/A"
|
|
494
|
+
"""Not sure"""
|
|
495
|
+
|
|
496
|
+
|
|
497
|
+
class NodeData(BaseModel):
|
|
498
|
+
"""Schema for node metadata"""
|
|
499
|
+
name: str
|
|
500
|
+
"""Name of the node"""
|
|
501
|
+
id: str
|
|
502
|
+
"""ID of the node"""
|
|
503
|
+
zone: str
|
|
504
|
+
"""Zone where the node is located within the ISO territory"""
|
|
505
|
+
type: NodeType
|
|
506
|
+
"""Identifier that indicates physical infrastructure associated with this node"""
|
|
507
|
+
da_start_year: float
|
|
508
|
+
"""First year in the Day Ahead (DA) market price dataset for this node"""
|
|
509
|
+
da_end_year: float
|
|
510
|
+
"""Final year in the Day Ahead (DA) market price dataset for this node"""
|
|
511
|
+
rt_start_year: int
|
|
512
|
+
"""First year in the Real Time (RT) market price dataset for this node"""
|
|
513
|
+
rt_end_year: int
|
|
514
|
+
"""Final year in the Real Time (RT) market price dataset for this node"""
|
|
515
|
+
substation: t.Optional[str] = None
|
|
516
|
+
"""Indicator of the grid substation associated with this node (not always present)"""
|
|
517
|
+
|
|
518
|
+
class PriceTimeSeries(BaseModel):
|
|
519
|
+
"""Schema for pricing data associated with a particular energy price node or ancillary pricing region"""
|
|
520
|
+
datetimes: list[str]
|
|
521
|
+
"""Beginning-of-interval datetimes for the hourly pricing given in local time.
|
|
522
|
+
|
|
523
|
+
- For energy prices, the datetimes are timezone-naive (no timezone identifier) but given in the local timezone
|
|
524
|
+
(i.e. including Daylight Savings Time or DST). E.g. The start of the year 2022 in ERCOT is given as
|
|
525
|
+
`'2022-01-01T00:00:00'` as opposed to `'2022-01-01T00:00:00-6:00'`. Leap days are represented by a single hour,
|
|
526
|
+
which should be dropped as a post-processing step.
|
|
527
|
+
- For ancillary prices, the datetimes are in local standard time (i.e. not including DST) but appear to be in
|
|
528
|
+
UTC ("Z" timezone identifier). E.g. The start of the year 2022 in ERCOT is given as `'2022-01-01T00:00:00Z'` and
|
|
529
|
+
not `'2022-01-01T00:00:00-6:00'`. Leap days are not included.
|
|
530
|
+
|
|
531
|
+
"""
|
|
532
|
+
prices: list[float]
|
|
533
|
+
"""Average hourly settlement prices for hours represented by :attr:`datetimes`.
|
|
534
|
+
"""
|
|
535
|
+
|
|
536
|
+
class NodeSearchData(BaseModel):
|
|
537
|
+
"""Schema for search-specific node metadata. The `(name 'xxxx')` to the right of the field names can be ignored"""
|
|
538
|
+
node_name: str = Field(..., alias='node/name')
|
|
539
|
+
"""Name of the node"""
|
|
540
|
+
node_id: str = Field(..., alias='node/id')
|
|
541
|
+
"""ID of the node"""
|
|
542
|
+
node_iso: str = Field(..., alias='node/iso')
|
|
543
|
+
"""ISO that the node belongs to"""
|
|
544
|
+
node_longitude: float = Field(..., alias='node/lng')
|
|
545
|
+
"""longitude of the point on the electrical grid associated with the node"""
|
|
546
|
+
node_latitude: float = Field(..., alias='node/lat')
|
|
547
|
+
"""latitude of the point on the electrical grid associated with the node"""
|
|
548
|
+
node_distance_meters: t.Optional[float] = Field(default=None, alias='node/distance-meters')
|
|
549
|
+
"""Distance from the node to the `location` parameter passed to :meth:`~LMP.search_nodes`. Not present if
|
|
550
|
+
`location` is not given.
|
|
551
|
+
"""
|
|
552
|
+
|
|
553
|
+
|
|
554
|
+
class AncillaryRegionData(BaseModel):
|
|
555
|
+
"""Schema for ancillary region metadata"""
|
|
556
|
+
region: str
|
|
557
|
+
"""Name of the region"""
|
|
558
|
+
start_year: int
|
|
559
|
+
"""First year in price dataset for the region and specified service"""
|
|
560
|
+
da_end_year: int
|
|
561
|
+
"""Final year in price dataset for the region and specified service"""
|
|
562
|
+
|
|
563
|
+
|
|
564
|
+
class ModelStatus(BaseModel):
|
|
565
|
+
"""Schema for model status and results"""
|
|
566
|
+
status: str
|
|
567
|
+
"""Status of the scheduled run. Possible values are explained in
|
|
568
|
+
:ref:`Tyba Model Run Status Codes <status_codes>`"""
|
|
569
|
+
progress: t.Optional[str]
|
|
570
|
+
"""Percentage value indicating the progress towards completing a model simulation. Only present if :attr:`status`
|
|
571
|
+
is not ``'complete'``.
|
|
572
|
+
|
|
573
|
+
- Note that in some cases the simulation may involve multiple optimization iterations, and the progress may appear
|
|
574
|
+
to start over as each additional iteration is undertaken
|
|
575
|
+
|
|
576
|
+
"""
|
|
577
|
+
result: t.Optional[V0Results]
|
|
578
|
+
"""Model simulation results dictionary with schema defined depending on the model inputs, e.g. scheduling a
|
|
579
|
+
:class:`~generation_models.generation_models.PVGenerationModel` will return a dictionary with
|
|
580
|
+
schema :class:`~generation_models.v0_output_schema.GenerationModelResults`. Only present if
|
|
581
|
+
:attr:`status` is ``'complete'``"""
|
|
582
|
+
|
|
583
|
+
|
tyba_client/forecast.py
CHANGED
|
@@ -3,6 +3,7 @@ from typing import List, Optional
|
|
|
3
3
|
|
|
4
4
|
|
|
5
5
|
class Forecast(object):
|
|
6
|
+
"""-"""
|
|
6
7
|
def __init__(self, client):
|
|
7
8
|
self.client = client
|
|
8
9
|
|
|
@@ -22,6 +23,18 @@ class Forecast(object):
|
|
|
22
23
|
prediction_lead_time_mins=None,
|
|
23
24
|
horizon_mins=None,
|
|
24
25
|
):
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
:param object_name:
|
|
29
|
+
:param product:
|
|
30
|
+
:param start_time:
|
|
31
|
+
:param end_time:
|
|
32
|
+
:param forecast_type:
|
|
33
|
+
:param predictions_per_hour:
|
|
34
|
+
:param prediction_lead_time_mins:
|
|
35
|
+
:param horizon_mins:
|
|
36
|
+
:return:
|
|
37
|
+
"""
|
|
25
38
|
return self.get(
|
|
26
39
|
"most_recent_forecast",
|
|
27
40
|
params={
|
|
@@ -49,6 +62,19 @@ class Forecast(object):
|
|
|
49
62
|
horizon_mins=None,
|
|
50
63
|
|
|
51
64
|
):
|
|
65
|
+
"""
|
|
66
|
+
|
|
67
|
+
:param object_name:
|
|
68
|
+
:param product:
|
|
69
|
+
:param start_time:
|
|
70
|
+
:param end_time:
|
|
71
|
+
:param quantiles:
|
|
72
|
+
:param forecast_type:
|
|
73
|
+
:param predictions_per_hour:
|
|
74
|
+
:param prediction_lead_time_mins:
|
|
75
|
+
:param horizon_mins:
|
|
76
|
+
:return:
|
|
77
|
+
"""
|
|
52
78
|
return self.get(
|
|
53
79
|
"most_recent_probabilistic_forecast",
|
|
54
80
|
params={
|
|
@@ -78,6 +104,21 @@ class Forecast(object):
|
|
|
78
104
|
prediction_lead_time_mins=None,
|
|
79
105
|
horizon_mins=None,
|
|
80
106
|
):
|
|
107
|
+
"""
|
|
108
|
+
|
|
109
|
+
:param object_name:
|
|
110
|
+
:param product:
|
|
111
|
+
:param start_time:
|
|
112
|
+
:param end_time:
|
|
113
|
+
:param days_ago:
|
|
114
|
+
:param before_time:
|
|
115
|
+
:param exact_vintage:
|
|
116
|
+
:param forecast_type:
|
|
117
|
+
:param predictions_per_hour:
|
|
118
|
+
:param prediction_lead_time_mins:
|
|
119
|
+
:param horizon_mins:
|
|
120
|
+
:return:
|
|
121
|
+
"""
|
|
81
122
|
return self.get(
|
|
82
123
|
"vintaged_forecast",
|
|
83
124
|
params={
|
|
@@ -110,6 +151,22 @@ class Forecast(object):
|
|
|
110
151
|
prediction_lead_time_mins=None,
|
|
111
152
|
horizon_mins=None,
|
|
112
153
|
):
|
|
154
|
+
"""
|
|
155
|
+
|
|
156
|
+
:param object_name:
|
|
157
|
+
:param product:
|
|
158
|
+
:param start_time:
|
|
159
|
+
:param end_time:
|
|
160
|
+
:param quantiles:
|
|
161
|
+
:param days_ago:
|
|
162
|
+
:param before_time:
|
|
163
|
+
:param exact_vintage:
|
|
164
|
+
:param forecast_type:
|
|
165
|
+
:param predictions_per_hour:
|
|
166
|
+
:param prediction_lead_time_mins:
|
|
167
|
+
:param horizon_mins:
|
|
168
|
+
:return:
|
|
169
|
+
"""
|
|
113
170
|
return self.get(
|
|
114
171
|
"vintaged_probabilistic_forecast",
|
|
115
172
|
params={
|
|
@@ -139,6 +196,18 @@ class Forecast(object):
|
|
|
139
196
|
prediction_lead_time_mins=None,
|
|
140
197
|
horizon_mins=None,
|
|
141
198
|
):
|
|
199
|
+
"""
|
|
200
|
+
|
|
201
|
+
:param object_name:
|
|
202
|
+
:param product:
|
|
203
|
+
:param vintage_start_time:
|
|
204
|
+
:param vintage_end_time:
|
|
205
|
+
:param forecast_type:
|
|
206
|
+
:param predictions_per_hour:
|
|
207
|
+
:param prediction_lead_time_mins:
|
|
208
|
+
:param horizon_mins:
|
|
209
|
+
:return:
|
|
210
|
+
"""
|
|
142
211
|
return self.get(
|
|
143
212
|
"forecasts_by_vintage",
|
|
144
213
|
params={
|
|
@@ -165,6 +234,19 @@ class Forecast(object):
|
|
|
165
234
|
prediction_lead_time_mins=None,
|
|
166
235
|
horizon_mins=None,
|
|
167
236
|
):
|
|
237
|
+
"""
|
|
238
|
+
|
|
239
|
+
:param object_name:
|
|
240
|
+
:param product:
|
|
241
|
+
:param quantiles:
|
|
242
|
+
:param vintage_start_time:
|
|
243
|
+
:param vintage_end_time:
|
|
244
|
+
:param forecast_type:
|
|
245
|
+
:param predictions_per_hour:
|
|
246
|
+
:param prediction_lead_time_mins:
|
|
247
|
+
:param horizon_mins:
|
|
248
|
+
:return:
|
|
249
|
+
"""
|
|
168
250
|
return self.get(
|
|
169
251
|
"probabilistic_forecasts_by_vintage",
|
|
170
252
|
params={
|
|
@@ -184,6 +266,14 @@ class Forecast(object):
|
|
|
184
266
|
self, object_name: str, product: str, start_time: datetime, end_time: datetime,
|
|
185
267
|
predictions_per_hour: Optional[int] = None
|
|
186
268
|
):
|
|
269
|
+
"""
|
|
270
|
+
|
|
271
|
+
:param object_name:
|
|
272
|
+
:param product:
|
|
273
|
+
:param start_time:
|
|
274
|
+
:param end_time:
|
|
275
|
+
:return:
|
|
276
|
+
"""
|
|
187
277
|
return self.get(
|
|
188
278
|
"actuals",
|
|
189
279
|
params={
|
tyba_client/io.py
CHANGED
|
@@ -5,6 +5,11 @@ from generation_models import (
|
|
|
5
5
|
ONDEfficiencyCurve,
|
|
6
6
|
ONDTemperatureDerateCurve,
|
|
7
7
|
)
|
|
8
|
+
from warnings import warn
|
|
9
|
+
|
|
10
|
+
warn("""tyba_client.io is deprecated in favor of using generation_models.utils.pvsyst_readers. Tyba will
|
|
11
|
+
cease to support tyba_client.io on 6/1/2025. See https://docs.tybaenergy.com/api/index.html or reach out
|
|
12
|
+
to us for help migrating.""", FutureWarning)
|
|
8
13
|
|
|
9
14
|
|
|
10
15
|
def read_pvsyst_file(path: str) -> dict:
|
|
@@ -53,7 +58,7 @@ def pv_module_from_pan(
|
|
|
53
58
|
bifacial_ground_clearance_height=1.0,
|
|
54
59
|
bifacial_transmission_factor: float = 0.013,
|
|
55
60
|
) -> PVModuleMermoudLejeune:
|
|
56
|
-
"""
|
|
61
|
+
"""DEPRECATED. See :func:`generation_models.utils.pvsyst_readers.pv_module_from_pan`."""
|
|
57
62
|
pan_blob = read_pvsyst_file(pan_file)
|
|
58
63
|
data = pan_blob["PVObject_"]["items"]
|
|
59
64
|
commercial = data["PVObject_Commercial"]["items"]
|
|
@@ -103,7 +108,7 @@ def pv_module_from_pan(
|
|
|
103
108
|
|
|
104
109
|
|
|
105
110
|
def inverter_from_ond(ond_file: str, includes_xfmr: bool = True) -> ONDInverter:
|
|
106
|
-
"""
|
|
111
|
+
"""DEPRECATED. See :func:`generation_models.utils.pvsyst_readers.inverter_from_ond`."""
|
|
107
112
|
ond = read_pvsyst_file(ond_file)
|
|
108
113
|
data = ond["PVObject_"]["items"]
|
|
109
114
|
converter = data["Converter"]["items"]
|
tyba_client/operations.py
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import datetime
|
|
2
|
+
import json
|
|
2
3
|
from datetime import date
|
|
3
|
-
from typing import Optional
|
|
4
|
+
from typing import Optional, Literal
|
|
4
5
|
|
|
5
6
|
|
|
6
7
|
class Operations(object):
|
|
@@ -12,6 +13,10 @@ class Operations(object):
|
|
|
12
13
|
response.raise_for_status()
|
|
13
14
|
return response.text
|
|
14
15
|
|
|
16
|
+
|
|
17
|
+
def post(self, route, json):
|
|
18
|
+
return self.client.post(f"operations/{route}", json=json)
|
|
19
|
+
|
|
15
20
|
def performance_report(
|
|
16
21
|
self,
|
|
17
22
|
start_date: date,
|
|
@@ -82,3 +87,58 @@ class Operations(object):
|
|
|
82
87
|
if org_id:
|
|
83
88
|
params["org_id"] = org_id
|
|
84
89
|
return self.get("internal_api/assets", params=params)
|
|
90
|
+
|
|
91
|
+
def set_asset_overrides(
|
|
92
|
+
self,
|
|
93
|
+
asset_names: list[str],
|
|
94
|
+
field: str,
|
|
95
|
+
aggregation: Literal["global", "single_day", "single_day_hourly", "12x24"],
|
|
96
|
+
values,
|
|
97
|
+
service: Optional[str] = None,
|
|
98
|
+
date: Optional[datetime.date] = None,
|
|
99
|
+
):
|
|
100
|
+
assumption = {
|
|
101
|
+
"field": field,
|
|
102
|
+
"data": {"aggregation":aggregation }
|
|
103
|
+
}
|
|
104
|
+
match aggregation:
|
|
105
|
+
case "12x24":
|
|
106
|
+
assumption["data"] = {"aggregation": "12x24"} | values
|
|
107
|
+
case "single_day_hourly":
|
|
108
|
+
assumption["data"] = {
|
|
109
|
+
"aggregation": "single_day_hourly",
|
|
110
|
+
"date": date,
|
|
111
|
+
"values": values
|
|
112
|
+
}
|
|
113
|
+
case "single_day":
|
|
114
|
+
assumption["data"] = {
|
|
115
|
+
"aggregation": "single_day_hourly",
|
|
116
|
+
"values": values,
|
|
117
|
+
"date": date
|
|
118
|
+
}
|
|
119
|
+
case "global":
|
|
120
|
+
assumption["data"] = {
|
|
121
|
+
"aggregation": "global",
|
|
122
|
+
"value": values
|
|
123
|
+
}
|
|
124
|
+
if service:
|
|
125
|
+
assumption["service"] = service
|
|
126
|
+
request_data = {"asset_names": asset_names, "assumption": assumption}
|
|
127
|
+
|
|
128
|
+
res = self.post("internal_api/assets/override/", json=request_data)
|
|
129
|
+
if res.status_code != 200:
|
|
130
|
+
return {
|
|
131
|
+
"status_code": res.status_code,
|
|
132
|
+
"reason": res.reason,
|
|
133
|
+
"message": res.text
|
|
134
|
+
}
|
|
135
|
+
else:
|
|
136
|
+
return json.loads(res.text)
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def overrides_schema(
|
|
141
|
+
self,
|
|
142
|
+
):
|
|
143
|
+
|
|
144
|
+
return json.loads(self.get("internal_api/overrides_schema"))
|
tyba_client/solar_resource.py
CHANGED
|
@@ -8,6 +8,12 @@ from generation_models import SolarResource, SolarResourceTimeSeries
|
|
|
8
8
|
from requests.exceptions import HTTPError
|
|
9
9
|
import typing as t
|
|
10
10
|
from io import StringIO
|
|
11
|
+
from warnings import warn
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
warn("""tyba_client.solar_resource is deprecated in favor of using generation_models.utils.psm_readers. Tyba will
|
|
15
|
+
cease to support tyba_client.solar_resource on 6/1/2025. See https://docs.tybaenergy.com/api/index.html or reach out
|
|
16
|
+
to us for help migrating.""", FutureWarning)
|
|
11
17
|
|
|
12
18
|
|
|
13
19
|
@dataclass
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
2
|
Name: tyba-client
|
|
3
|
-
Version: 0.4.
|
|
3
|
+
Version: 0.4.16
|
|
4
4
|
Summary: A Python API client for the Tyba Public API
|
|
5
5
|
License: MIT
|
|
6
6
|
Author: Tyler Nisonoff
|
|
@@ -19,6 +19,7 @@ Requires-Dist: generation-models (>=0.10.6,<0.11.0)
|
|
|
19
19
|
Requires-Dist: marshmallow (>=3.12.1,<4.0.0)
|
|
20
20
|
Requires-Dist: pandas (>=1.3.2,<2.0.0) ; python_version < "3.9"
|
|
21
21
|
Requires-Dist: pandas (>=2.0.0,<3.0.0) ; python_version >= "3.9"
|
|
22
|
+
Requires-Dist: pydantic (>=2.10.6,<3.0.0)
|
|
22
23
|
Requires-Dist: requests (>=2.25.1,<3.0.0)
|
|
23
24
|
Requires-Dist: structlog (>=24.1.0,<25.0.0)
|
|
24
25
|
Description-Content-Type: text/markdown
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
tyba_client/__init__.py,sha256=42STGor_9nKYXumfeV5tiyD_M8VdcddX7CEexmibPBk,22
|
|
2
|
+
tyba_client/client.py,sha256=RAvjZkjWDDeY4eJXD2oELF4E1DOa02AqAcvWftsxISY,23295
|
|
3
|
+
tyba_client/forecast.py,sha256=P9GuKPrTCQpdxatSPCck5dezfMIe7otCjOiylyPVh-s,8383
|
|
4
|
+
tyba_client/io.py,sha256=0FVRtUjSDzyWYBvIMSJH7bCclCRqD2Y-pbcsJ-Ufyo0,6226
|
|
5
|
+
tyba_client/models.py,sha256=NOo39qStMkWBhfLPkUu37MaKMe3E2yFc5KAzUnFy96M,17569
|
|
6
|
+
tyba_client/operations.py,sha256=cOAyeV2vz_6FEY4jlOPsMNLmJ8TvIaZgfyYJjMZEejg,4175
|
|
7
|
+
tyba_client/solar_resource.py,sha256=0Jte2cZpE-USHeW-qac4AN1mI2tmrC-VSahnbs4v3Bs,3683
|
|
8
|
+
tyba_client/utils.py,sha256=n4tUBGlQIwxbLqJcQRiAIbIJA0DaLSjbAxakhukqaeE,876
|
|
9
|
+
tyba_client-0.4.16.dist-info/LICENSE,sha256=LbMfEdjEK-IRzvCfdEBhn9UCANze0Rc7hWrQTEj_xvU,1079
|
|
10
|
+
tyba_client-0.4.16.dist-info/METADATA,sha256=v4flCDkfvS6JTnCAEEsNmVkywi0FFNJ5MImlCPtDgyE,1324
|
|
11
|
+
tyba_client-0.4.16.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
|
|
12
|
+
tyba_client-0.4.16.dist-info/RECORD,,
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
The MIT License (MIT)
|
|
2
|
-
|
|
3
|
-
Copyright (c) 2021 Tyba Energy.
|
|
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,13 +0,0 @@
|
|
|
1
|
-
LICENSE,sha256=LbMfEdjEK-IRzvCfdEBhn9UCANze0Rc7hWrQTEj_xvU,1079
|
|
2
|
-
tyba_client/__init__.py,sha256=42STGor_9nKYXumfeV5tiyD_M8VdcddX7CEexmibPBk,22
|
|
3
|
-
tyba_client/client.py,sha256=6hFeAF5rptn6b1Az6sM8j1b2mLN04k_Qc_LOsdZRsBQ,6576
|
|
4
|
-
tyba_client/forecast.py,sha256=ehtIZB_RrhXbic5iadm8gGTD5YEZAB0agrFqj61A2mk,6245
|
|
5
|
-
tyba_client/io.py,sha256=iyiQrpM1xg9XAoXlU6JvNB1uxRK77keJ8gAG-fefV2U,5783
|
|
6
|
-
tyba_client/models.py,sha256=NOo39qStMkWBhfLPkUu37MaKMe3E2yFc5KAzUnFy96M,17569
|
|
7
|
-
tyba_client/operations.py,sha256=WkAr_e-15C0wIzP9l_a0uRJGqhObUIEG_iDWo_17TqU,2316
|
|
8
|
-
tyba_client/solar_resource.py,sha256=oUD6-qUJhiIjrHbSzSL0X4asGgU4lRBocyy0MkA3keY,3379
|
|
9
|
-
tyba_client/utils.py,sha256=n4tUBGlQIwxbLqJcQRiAIbIJA0DaLSjbAxakhukqaeE,876
|
|
10
|
-
tyba_client-0.4.13.dist-info/LICENSE,sha256=LbMfEdjEK-IRzvCfdEBhn9UCANze0Rc7hWrQTEj_xvU,1079
|
|
11
|
-
tyba_client-0.4.13.dist-info/METADATA,sha256=wab_EgjfsdNK4Ja0vSpJX_L9w1DKGK_J7MDXdXHVYl4,1282
|
|
12
|
-
tyba_client-0.4.13.dist-info/WHEEL,sha256=Nq82e9rUAnEjt98J6MlVmMCZb-t9cYE2Ir1kpBmnWfs,88
|
|
13
|
-
tyba_client-0.4.13.dist-info/RECORD,,
|
|
File without changes
|