tyba-client 0.4.18__py3-none-any.whl → 0.5.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.

Potentially problematic release.


This version of tyba-client might be problematic. Click here for more details.

tyba_client/__init__.py CHANGED
@@ -1 +1,4 @@
1
- __version__ = "0.4.0"
1
+ from importlib.metadata import version
2
+
3
+
4
+ __version__ = version("tyba-client")
tyba_client/client.py CHANGED
@@ -393,16 +393,6 @@ class Client(object):
393
393
  url = "get-status/" + run_id
394
394
  return self.get(url)
395
395
 
396
- def get_status_v1(self, run_id: str):
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
- """
405
- return self.get(f"get-status/{run_id}", params={"fmt": "v1"})
406
396
 
407
397
  @staticmethod
408
398
  def _wait_on_result(
@@ -442,24 +432,6 @@ class Client(object):
442
432
  run_id, wait_time, log_progress, getter=self.get_status
443
433
  )
444
434
 
445
- def wait_on_result_v1(
446
- self, run_id: str, wait_time: int = 5, log_progress: bool = False
447
- ):
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
- """
458
- res = self._wait_on_result(
459
- run_id, wait_time, log_progress, getter=self.get_status_v1
460
- )
461
- return parse_v1_result(res)
462
-
463
435
 
464
436
  def parse_v1_result(res: dict):
465
437
  """:meta private:"""
tyba_client/operations.py CHANGED
@@ -3,7 +3,9 @@ import json
3
3
  from datetime import date
4
4
  from typing import Optional, Literal
5
5
 
6
- from tyba_client.models import ValidationError
6
+
7
+ class ValidationError(ValueError):
8
+ pass
7
9
 
8
10
 
9
11
  class Operations(object):
@@ -1,27 +1,21 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: tyba-client
3
- Version: 0.4.18
3
+ Version: 0.5.0
4
4
  Summary: A Python API client for the Tyba Public API
5
5
  License: MIT
6
6
  Author: Tyler Nisonoff
7
7
  Author-email: tyler@tybaenergy.com
8
- Requires-Python: >=3.8,<4.0
8
+ Requires-Python: >=3.10,<4.0
9
9
  Classifier: License :: OSI Approved :: MIT License
10
10
  Classifier: Programming Language :: Python :: 3
11
- Classifier: Programming Language :: Python :: 3.8
12
- Classifier: Programming Language :: Python :: 3.9
13
11
  Classifier: Programming Language :: Python :: 3.10
14
12
  Classifier: Programming Language :: Python :: 3.11
15
13
  Classifier: Programming Language :: Python :: 3.12
16
14
  Classifier: Programming Language :: Python :: 3.13
17
- Requires-Dist: dataclasses-json (>=0.6.4,<0.7.0)
18
- Requires-Dist: generation-models (>=0.10.6,<0.11.0)
19
- Requires-Dist: marshmallow (>=3.12.1,<4.0.0)
20
- Requires-Dist: pandas (>=1.3.2,<2.0.0) ; python_version < "3.9"
21
- Requires-Dist: pandas (>=2.0.0,<3.0.0) ; python_version >= "3.9"
22
- Requires-Dist: pydantic (>=2.10.6,<3.0.0)
23
- Requires-Dist: requests (>=2.25.1,<3.0.0)
24
- Requires-Dist: structlog (>=24.1.0,<25.0.0)
15
+ Requires-Dist: generation-models (>=0.10.8,<0.11.0)
16
+ Requires-Dist: pandas (>=2.3.2,<3.0.0)
17
+ Requires-Dist: requests (>=2.32.5,<3.0.0)
18
+ Requires-Dist: structlog (>=25.4.0,<26.0.0)
25
19
  Description-Content-Type: text/markdown
26
20
 
27
21
  <h1 align="center">
@@ -0,0 +1,9 @@
1
+ tyba_client/__init__.py,sha256=kM0twWsmGVgn2TLtq72J8OIZu1x7mtFSapNc67WkhZY,78
2
+ tyba_client/client.py,sha256=GFGeFoVHDHEFrwfmNbtboY5s5I-5_xE4e-h5WBxAE24,21753
3
+ tyba_client/forecast.py,sha256=P9GuKPrTCQpdxatSPCck5dezfMIe7otCjOiylyPVh-s,8383
4
+ tyba_client/operations.py,sha256=_j6ErTmEORYLxj815BX0ZLtIkmx-SE84heADJ136UD0,5388
5
+ tyba_client/utils.py,sha256=n4tUBGlQIwxbLqJcQRiAIbIJA0DaLSjbAxakhukqaeE,876
6
+ tyba_client-0.5.0.dist-info/LICENSE,sha256=LbMfEdjEK-IRzvCfdEBhn9UCANze0Rc7hWrQTEj_xvU,1079
7
+ tyba_client-0.5.0.dist-info/METADATA,sha256=Eb39BSUVvdBUSmXHjYuLef3MMWxZEn1_puFOfJz3lFI,998
8
+ tyba_client-0.5.0.dist-info/WHEEL,sha256=fGIA9gx4Qxk2KDKeNJCbOEwSrmLtjWCwzBz351GyrPQ,88
9
+ tyba_client-0.5.0.dist-info/RECORD,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: poetry-core 2.1.3
2
+ Generator: poetry-core 2.1.2
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
tyba_client/io.py DELETED
@@ -1,163 +0,0 @@
1
- from __future__ import annotations
2
- from generation_models import (
3
- PVModuleMermoudLejeune,
4
- ONDInverter,
5
- ONDEfficiencyCurve,
6
- ONDTemperatureDerateCurve,
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)
13
-
14
-
15
- def read_pvsyst_file(path: str) -> dict:
16
- try:
17
- with open(path, "r", encoding="utf-8-sig") as f:
18
- blob = {}
19
- trace = [blob]
20
- lines = [line for line in f.readlines() if "=" in line]
21
- indentations = [len(line) - len(line.lstrip()) for line in lines]
22
- for indentation, line in zip(indentations, lines):
23
- if divmod(indentation, 2)[1] != 0:
24
- raise PVSystFileError(f"invalid indentation at {line}")
25
- structure = [
26
- (y - x) // 2 for x, y in zip(indentations[:-1], indentations[1:])
27
- ]
28
- structure.append(0)
29
- for line, typ in zip(lines, structure):
30
- stripped = line.strip()
31
- k, v = stripped.split("=")
32
- if typ == 0:
33
- trace[-1][k] = v
34
- elif typ == 1:
35
- trace[-1][k] = {"type": v, "items": {}}
36
- trace.append(trace[-1][k]["items"])
37
- elif typ < 0:
38
- trace[-1][k] = v
39
- for _ in range(-1 * typ):
40
- trace.pop()
41
- else:
42
- raise PVSystFileError(f"invalid structure at {line}")
43
- except UnicodeDecodeError:
44
- raise PVSystFileError(
45
- f"File not utf-8 encoded. Please encode the file to utf-8 and try again"
46
- )
47
- return blob
48
-
49
-
50
- class PVSystFileError(ValueError):
51
- """:meta private:"""
52
-
53
- pass
54
-
55
-
56
- def pv_module_from_pan(
57
- pan_file: str,
58
- bifacial_ground_clearance_height=1.0,
59
- bifacial_transmission_factor: float = 0.013,
60
- ) -> PVModuleMermoudLejeune:
61
- """DEPRECATED. See :func:`generation_models.utils.pvsyst_readers.pv_module_from_pan`."""
62
- pan_blob = read_pvsyst_file(pan_file)
63
- data = pan_blob["PVObject_"]["items"]
64
- commercial = data["PVObject_Commercial"]["items"]
65
- if "PVObject_IAM" in data:
66
- iam_points = [
67
- v.split(",")
68
- for k, v in data["PVObject_IAM"]["items"]["IAMProfile"]["items"].items()
69
- if k.startswith("Point_")
70
- ]
71
- iam_angles = [float(v[0]) for v in iam_points]
72
- iam_values = [float(v[1]) for v in iam_points]
73
- else:
74
- iam_angles = None
75
- iam_values = None
76
- return PVModuleMermoudLejeune(
77
- bifacial="BifacialityFactor" in data,
78
- bifacial_transmission_factor=bifacial_transmission_factor,
79
- bifaciality=float(data.get("BifacialityFactor", 0.65)),
80
- bifacial_ground_clearance_height=bifacial_ground_clearance_height,
81
- n_parallel=int(data["NCelP"]),
82
- n_diodes=int(data["NDiode"]),
83
- n_series=int(data["NCelS"]),
84
- t_ref=float(data["TRef"]),
85
- s_ref=float(data["GRef"]),
86
- i_sc_ref=float(data["Isc"]),
87
- v_oc_ref=float(data["Voc"]),
88
- i_mp_ref=float(data["Imp"]),
89
- v_mp_ref=float(data["Vmp"]),
90
- alpha_sc=float(data["muISC"]) * 1e-3, # TODO: check units
91
- beta_oc=float(data["muVocSpec"]) * 1e-3,
92
- n_0=float(data["Gamma"]),
93
- mu_n=float(data["muGamma"]),
94
- r_sh_ref=float(data["RShunt"]),
95
- r_s=float(data["RSerie"]),
96
- r_sh_0=float(data["Rp_0"]),
97
- r_sh_exp=float(data["Rp_Exp"]),
98
- tech=data["Technol"],
99
- length=float(commercial["Height"]),
100
- width=float(commercial["Width"]),
101
- # faiman cell temp model used by PVSyst
102
- t_c_fa_alpha=float(data["Absorb"]),
103
- # IAM
104
- iam_c_cs_iam_value=iam_values,
105
- iam_c_cs_inc_angle=iam_angles,
106
- custom_d2_mu_tau=data.get("D2MuTau"),
107
- )
108
-
109
-
110
- def inverter_from_ond(ond_file: str, includes_xfmr: bool = True) -> ONDInverter:
111
- """DEPRECATED. See :func:`generation_models.utils.pvsyst_readers.inverter_from_ond`."""
112
- ond = read_pvsyst_file(ond_file)
113
- data = ond["PVObject_"]["items"]
114
- converter = data["Converter"]["items"]
115
- voltage_curve_points = [float(v) for v in converter["VNomEff"].split(",") if v]
116
- if len(voltage_curve_points) != 3:
117
- raise NotImplementedError(
118
- "OND Inverter only accepts voltage curves of length 3"
119
- )
120
- temp_derate_curve = ONDTemperatureDerateCurve(
121
- ambient_temp=[
122
- -300,
123
- float(converter["TPMax"]),
124
- float(converter["TPNom"]),
125
- float(converter["TPLim1"]),
126
- float(converter["TPLimAbs"]),
127
- ],
128
- max_ac_power=[
129
- float(converter["PMaxOUT"]) * 1e3,
130
- float(converter["PMaxOUT"]) * 1e3,
131
- float(converter["PNomConv"]) * 1e3,
132
- float(converter["PLim1"]) * 1e3,
133
- float(converter.get("PlimAbs", 0.0)) * 1e3,
134
- ],
135
- )
136
- raw_power_curves = [converter[f"ProfilPIOV{i}"]["items"] for i in [1, 2, 3]]
137
- power_curves = []
138
- for curve in raw_power_curves:
139
- points = [
140
- [float(v) for v in curve[f"Point_{i}"].split(",")]
141
- for i in range(1, int(curve["NPtsEff"]) + 1)
142
- ]
143
- dc, ac = zip(*points)
144
- power_curves.append(ONDEfficiencyCurve(dc_power=dc, ac_power=ac))
145
- aux_loss = data.get("Aux_Loss")
146
- aux_loss_threshold = data.get("Aux_Thresh")
147
- if aux_loss is not None:
148
- aux_loss = float(aux_loss)
149
- aux_loss_threshold = float(aux_loss_threshold)
150
- return ONDInverter(
151
- mppt_low=float(converter["VMppMin"]),
152
- mppt_high=float(converter["VMPPMax"]),
153
- paco=float(converter["PMaxOUT"]) * 1e3,
154
- vdco=voltage_curve_points[1],
155
- temp_derate_curve=temp_derate_curve,
156
- nominal_voltages=voltage_curve_points,
157
- power_curves=power_curves,
158
- dc_turn_on=float(converter["PSeuil"]),
159
- pnt=float(data["Night_Loss"]),
160
- aux_loss=aux_loss,
161
- aux_loss_threshold=aux_loss_threshold,
162
- includes_xfmr=includes_xfmr,
163
- )
tyba_client/models.py DELETED
@@ -1,626 +0,0 @@
1
- from __future__ import annotations
2
- from dataclasses import dataclass, field
3
- from enum import Enum
4
- from dataclasses_json import config, dataclass_json
5
- import pandas as pd
6
- import typing as t
7
- from .io import read_pvsyst_file
8
- from .solar_resource import psm_column_map
9
- from warnings import warn
10
-
11
- from tyba_client.utils import string_enum
12
-
13
-
14
- class ValidationError(ValueError):
15
- pass
16
-
17
-
18
- def opt_field():
19
- return field(default=None, metadata=config(exclude=lambda x: x is None))
20
-
21
-
22
- @dataclass_json
23
- @dataclass
24
- class FixedTilt(object):
25
- tilt: float
26
-
27
-
28
- @dataclass_json
29
- @dataclass
30
- class SingleAxisTracking(object):
31
- rotation_limit: float = 45.0
32
- backtrack: bool = True
33
-
34
-
35
- @dataclass_json
36
- @dataclass
37
- class BaseSystemDesign(object):
38
- dc_capacity: float
39
- ac_capacity: float
40
- poi_limit: float
41
-
42
-
43
- @dataclass_json
44
- @dataclass
45
- class SystemDesign(BaseSystemDesign):
46
- gcr: float
47
- tracking: t.Union[FixedTilt, SingleAxisTracking]
48
- modules_per_string: t.Optional[int] = opt_field()
49
- strings_in_parallel: t.Optional[int] = opt_field()
50
- azimuth: t.Optional[float] = opt_field()
51
-
52
-
53
- @dataclass_json
54
- @dataclass
55
- class BaseInverter(object):
56
- mppt_low: float
57
- mppt_high: float
58
- paco: float
59
- vdco: float
60
- pnt: float
61
-
62
-
63
- @dataclass_json
64
- @dataclass
65
- class ONDTemperatureDerateCurve(object):
66
- ambient_temp: t.List[float]
67
- max_ac_power: t.List[float]
68
-
69
-
70
- @dataclass_json
71
- @dataclass
72
- class ONDEfficiencyCurve(object):
73
- dc_power: t.List[float]
74
- ac_power: t.List[float]
75
-
76
-
77
- @dataclass_json
78
- @dataclass
79
- class ONDInverter(BaseInverter):
80
- temp_derate_curve: ONDTemperatureDerateCurve
81
- nominal_voltages: t.List[float]
82
- power_curves: t.List[ONDEfficiencyCurve]
83
- dc_turn_on: float
84
- aux_loss: t.Optional[float] = opt_field()
85
- aux_loss_threshold: t.Optional[float] = opt_field()
86
- includes_xfmr: t.Optional[bool] = True
87
-
88
- @classmethod
89
- def from_ond(cls, ond_file: str):
90
- ond = read_pvsyst_file(ond_file)
91
- data = ond["PVObject_"]["items"]
92
- converter = data["Converter"]["items"]
93
- voltage_curve_points = [float(v) for v in converter["VNomEff"].split(",") if v]
94
- if len(voltage_curve_points) != 3:
95
- raise NotImplementedError(
96
- "OND Inverter only accepts voltage curves of length 3"
97
- )
98
- temp_derate_curve = ONDTemperatureDerateCurve(
99
- ambient_temp=[
100
- -300,
101
- float(converter["TPMax"]),
102
- float(converter["TPNom"]),
103
- float(converter["TPLim1"]),
104
- float(converter["TPLimAbs"]),
105
- ],
106
- max_ac_power=[
107
- float(converter["PMaxOUT"]) * 1e3,
108
- float(converter["PMaxOUT"]) * 1e3,
109
- float(converter["PNomConv"]) * 1e3,
110
- float(converter["PLim1"]) * 1e3,
111
- float(converter.get("PlimAbs", 0.0)) * 1e3,
112
- ],
113
- )
114
- raw_power_curves = [converter[f"ProfilPIOV{i}"]["items"] for i in [1, 2, 3]]
115
- power_curves = []
116
- for curve in raw_power_curves:
117
- points = [
118
- [float(v) for v in curve[f"Point_{i}"].split(",")]
119
- for i in range(1, int(curve["NPtsEff"]) + 1)
120
- ]
121
- dc, ac = zip(*points)
122
- power_curves.append(ONDEfficiencyCurve(dc_power=dc, ac_power=ac))
123
- aux_loss = data.get("Aux_Loss")
124
- aux_loss_threshold = data.get("Aux_Thresh")
125
- if aux_loss is not None:
126
- aux_loss = float(aux_loss)
127
- aux_loss_threshold = float(aux_loss_threshold)
128
- return cls(
129
- mppt_low=float(converter["VMppMin"]),
130
- mppt_high=float(converter["VMPPMax"]),
131
- paco=float(converter["PMaxOUT"]) * 1e3,
132
- vdco=voltage_curve_points[1],
133
- temp_derate_curve=temp_derate_curve,
134
- nominal_voltages=voltage_curve_points,
135
- power_curves=power_curves,
136
- dc_turn_on=float(converter["PSeuil"]),
137
- pnt=float(data["Night_Loss"]),
138
- aux_loss=aux_loss,
139
- aux_loss_threshold=aux_loss_threshold,
140
- )
141
-
142
-
143
- @dataclass_json
144
- @dataclass
145
- class Inverter(BaseInverter):
146
- pso: float
147
- pdco: float
148
- c0: float
149
- c1: float
150
- c2: float
151
- c3: float
152
- vdcmax: float
153
- tdc: t.List[t.List[float]] = field(default_factory=lambda: [[1.0, 52.8, -0.021]])
154
- includes_xfmr: t.Optional[bool] = True
155
-
156
-
157
- @dataclass_json
158
- @dataclass
159
- class PVModule(object):
160
- bifacial: bool
161
- a_c: float
162
- n_s: float
163
- i_sc_ref: float
164
- v_oc_ref: float
165
- i_mp_ref: float
166
- v_mp_ref: float
167
- alpha_sc: float
168
- beta_oc: float
169
- t_noct: float
170
- a_ref: float
171
- i_l_ref: float
172
- i_o_ref: float
173
- r_s: float
174
- r_sh_ref: float
175
- adjust: float
176
- gamma_r: float
177
- bifacial_transmission_factor: float
178
- bifaciality: float
179
- bifacial_ground_clearance_height: float
180
-
181
-
182
- @string_enum
183
- class MermoudModuleTech(Enum):
184
- SiMono = "mtSiMono"
185
- SiPoly = "mtSiPoly"
186
- CdTe = "mtCdTe"
187
- CIS = "mtCIS"
188
- uCSi_aSiH = "mtuCSi_aSiH"
189
-
190
-
191
- @dataclass_json
192
- @dataclass
193
- class PVModuleMermoudLejeune(object):
194
- bifacial: bool
195
- bifacial_transmission_factor: float
196
- bifaciality: float
197
- bifacial_ground_clearance_height: float
198
- tech: MermoudModuleTech
199
- i_mp_ref: float
200
- i_sc_ref: float
201
- length: float
202
- n_diodes: int
203
- n_parallel: int
204
- n_series: int
205
- r_s: float
206
- r_sh_0: float
207
- r_sh_exp: float
208
- r_sh_ref: float
209
- s_ref: float
210
- t_c_fa_alpha: float
211
- t_ref: float
212
- v_mp_ref: float
213
- v_oc_ref: float
214
- width: float
215
- alpha_sc: float
216
- beta_oc: float
217
- mu_n: float
218
- n_0: float
219
- iam_c_cs_iam_value: t.Optional[t.List[float]] = opt_field()
220
- iam_c_cs_inc_angle: t.Optional[t.List[float]] = opt_field()
221
- custom_d2_mu_tau: t.Optional[float] = opt_field()
222
-
223
- @classmethod
224
- def from_pan(cls, pan_file: str):
225
- pan_blob = read_pvsyst_file(pan_file)
226
- data = pan_blob["PVObject_"]["items"]
227
- commercial = data["PVObject_Commercial"]["items"]
228
- if "PVObject_IAM" in data:
229
- iam_points = [
230
- v.split(",")
231
- for k, v in data["PVObject_IAM"]["items"]["IAMProfile"]["items"].items()
232
- if k.startswith("Point_")
233
- ]
234
- iam_angles = [float(v[0]) for v in iam_points]
235
- iam_values = [float(v[1]) for v in iam_points]
236
- else:
237
- iam_angles = None
238
- iam_values = None
239
-
240
- return cls(
241
- bifacial="BifacialityFactor" in data,
242
- bifacial_transmission_factor=0.013,
243
- bifaciality=float(data.get("BifacialityFactor", 0.65)),
244
- bifacial_ground_clearance_height=1.0,
245
- n_parallel=int(data["NCelP"]),
246
- n_diodes=int(data["NDiode"]),
247
- n_series=int(data["NCelS"]),
248
- t_ref=float(data["TRef"]),
249
- s_ref=float(data["GRef"]),
250
- i_sc_ref=float(data["Isc"]),
251
- v_oc_ref=float(data["Voc"]),
252
- i_mp_ref=float(data["Imp"]),
253
- v_mp_ref=float(data["Vmp"]),
254
- alpha_sc=float(data["muISC"]) * 1e-3, # TODO: check units
255
- beta_oc=float(data["muVocSpec"]) * 1e-3,
256
- n_0=float(data["Gamma"]),
257
- mu_n=float(data["muGamma"]),
258
- r_sh_ref=float(data["RShunt"]),
259
- r_s=float(data["RSerie"]),
260
- r_sh_0=float(data["Rp_0"]),
261
- r_sh_exp=float(data["Rp_Exp"]),
262
- tech=data["Technol"],
263
- length=float(commercial["Height"]),
264
- width=float(commercial["Width"]),
265
- # faiman cell temp model used by PVSyst
266
- t_c_fa_alpha=float(data["Absorb"]),
267
- # IAM
268
- iam_c_cs_iam_value=iam_values,
269
- iam_c_cs_inc_angle=iam_angles,
270
- custom_d2_mu_tau=data.get("D2MuTau"),
271
- )
272
-
273
-
274
- @dataclass_json
275
- @dataclass
276
- class Transformer(object):
277
- load_loss: float
278
- no_load_loss: float
279
- rating: t.Optional[float] = opt_field()
280
-
281
-
282
- @dataclass_json
283
- @dataclass
284
- class _DCLosses(object):
285
- dc_optimizer: t.Optional[float] = opt_field()
286
- enable_snow_model: t.Optional[bool] = opt_field()
287
- dc_wiring: t.Optional[float] = opt_field()
288
- soiling: t.Optional[t.List[float]] = opt_field()
289
- diodes_connections: t.Optional[float] = opt_field()
290
- mismatch: t.Optional[float] = opt_field()
291
- nameplate: t.Optional[float] = opt_field()
292
- rear_irradiance: t.Optional[float] = opt_field()
293
- lid: t.Optional[float] = opt_field()
294
- dc_array_adjustment: t.Optional[float] = opt_field()
295
- mppt_error: t.Optional[float] = opt_field()
296
-
297
-
298
- @dataclass_json
299
- @dataclass
300
- class ACLosses(object):
301
- ac_wiring: t.Optional[float] = opt_field()
302
- transformer_load: t.Optional[float] = opt_field()
303
- transformer_no_load: t.Optional[float] = opt_field()
304
- transmission: t.Optional[float] = opt_field()
305
- poi_adjustment: t.Optional[float] = opt_field()
306
- mv_transformer: t.Optional[Transformer] = opt_field()
307
- hv_transformer: t.Optional[Transformer] = opt_field()
308
-
309
- def __post_init__(self):
310
- if not (
311
- (self.transformer_load is None and self.transformer_no_load is None)
312
- or self.hv_transformer is None
313
- ):
314
- raise ValidationError(
315
- "Cannot provide hv_transformer if transformer_load or transformer_no_load are provided"
316
- )
317
-
318
-
319
- @dataclass_json
320
- @dataclass
321
- class Losses(_DCLosses, ACLosses):
322
- pass
323
-
324
-
325
- @dataclass_json
326
- @dataclass
327
- class Layout(object):
328
- orientation: t.Optional[str] = opt_field()
329
- vertical: t.Optional[int] = opt_field()
330
- horizontal: t.Optional[int] = opt_field()
331
- aspect_ratio: t.Optional[float] = opt_field()
332
-
333
-
334
- @dataclass_json
335
- @dataclass
336
- class SolarResourceTimeSeries(object):
337
- year: t.List[int]
338
- month: t.List[int]
339
- day: t.List[int]
340
- hour: t.List[int]
341
- minute: t.List[int]
342
- tdew: t.List[float]
343
- df: t.List[float]
344
- dn: t.List[float]
345
- gh: t.List[float]
346
- pres: t.List[float]
347
- tdry: t.List[float]
348
- wdir: t.List[float]
349
- wspd: t.List[float]
350
- alb: t.Optional[t.List[float]] = opt_field()
351
- snow: t.Optional[t.List[float]] = opt_field()
352
-
353
-
354
- @dataclass_json
355
- @dataclass
356
- class SolarResource(object):
357
- latitude: float
358
- longitude: float
359
- time_zone_offset: float
360
- elevation: float
361
- data: SolarResourceTimeSeries
362
- monthly_albedo: t.Optional[t.List[float]] = opt_field()
363
-
364
- @classmethod
365
- def from_csv(cls, filename: str) -> SolarResource:
366
- with open(filename) as f:
367
- _meta = [f.readline().split(",") for _ in range(2)]
368
- _data = pd.read_csv(f)
369
- meta = {k: v for k, v in zip(*_meta)}
370
- data = _data.rename(columns=psm_column_map)
371
- return cls(
372
- latitude=float(meta["Latitude"]),
373
- longitude=float(meta["Longitude"]),
374
- elevation=float(meta["Elevation"]),
375
- time_zone_offset=float(meta["Time Zone"]),
376
- data=data.to_dict(orient="list"),
377
- )
378
-
379
-
380
- @string_enum
381
- class ArrayDegradationMode(str, Enum):
382
- linear = "linear"
383
- compounding = "compounding"
384
-
385
-
386
- @dataclass_json
387
- @dataclass
388
- class PVModel(object):
389
- solar_resource: t.Union[t.Tuple[float, float], SolarResource]
390
- inverter: t.Union[str, BaseInverter]
391
- pv_module: t.Union[str, PVModule, PVModuleMermoudLejeune]
392
- system_design: SystemDesign
393
- losses: t.Optional[Losses] = opt_field()
394
- layout: t.Optional[Layout] = opt_field()
395
- project_term: t.Optional[int] = opt_field()
396
- array_degradation_rate: t.Optional[float] = opt_field()
397
- array_degradation_mode: t.Optional[ArrayDegradationMode] = "linear"
398
-
399
-
400
- PVGenerationModel = DetailedPVModel = PVModel
401
-
402
-
403
- @dataclass_json
404
- @dataclass
405
- class DCProductionProfile(object):
406
- power: t.List[float]
407
- voltage: t.List[float]
408
- ambient_temp: t.Optional[t.List[float]]
409
-
410
-
411
- @dataclass_json
412
- @dataclass
413
- class ACProductionProfile(object):
414
- power: t.List[float]
415
- ambient_temp: t.Optional[t.List[float]]
416
-
417
-
418
- @dataclass_json
419
- @dataclass
420
- class ACExternalGenerationModel(object):
421
- production_override: ACProductionProfile
422
- system_design: BaseSystemDesign
423
- losses: ACLosses = opt_field()
424
- time_interval_mins: t.Optional[int] = opt_field()
425
- project_term: t.Optional[int] = opt_field()
426
- project_term_units: t.Optional[str] = opt_field()
427
-
428
-
429
- @dataclass_json
430
- @dataclass
431
- class DCExternalGenerationModel(object):
432
- production_override: DCProductionProfile
433
- system_design: BaseSystemDesign
434
- inverter: t.Union[Inverter, ONDInverter, str]
435
- losses: ACLosses = opt_field()
436
- time_interval_mins: t.Optional[int] = opt_field()
437
- project_term: t.Optional[int] = opt_field()
438
- project_term_units: t.Optional[str] = opt_field()
439
-
440
-
441
- GenerationModel = t.Union[
442
- PVGenerationModel, DCExternalGenerationModel, ACExternalGenerationModel
443
- ]
444
-
445
-
446
- @dataclass_json
447
- @dataclass
448
- class BatteryHVAC(object):
449
- container_temperature: float
450
- cop: float
451
- u_ambient: float
452
- discharge_efficiency_container: float
453
- charge_efficiency_container: float
454
- aux_xfmr_efficiency: float
455
-
456
-
457
- @dataclass_json
458
- @dataclass
459
- class TableCapDegradationModel(object):
460
- annual_capacity_derates: t.List[float]
461
-
462
-
463
- @dataclass_json
464
- @dataclass
465
- class TableEffDegradationModel(object):
466
- annual_efficiency_derates: t.List[float]
467
-
468
-
469
- @dataclass_json
470
- @dataclass
471
- class Battery(object):
472
- power_capacity: float
473
- energy_capacity: float
474
- charge_efficiency: float
475
- discharge_efficiency: float
476
- term: float
477
- degradation_rate: t.Optional[float] = opt_field()
478
- degradation_annual_cycles: t.Optional[float] = 365
479
- hvac: t.Optional[BatteryHVAC] = opt_field()
480
- capacity_degradation_model: t.Optional[TableCapDegradationModel] = opt_field()
481
- efficiency_degradation_model: t.Optional[TableEffDegradationModel] = opt_field()
482
-
483
-
484
- @dataclass_json
485
- @dataclass
486
- class StorageInputs(object):
487
- batteries: t.List[Battery]
488
- cycling_cost_adder: t.Optional[float] = 0
489
- annual_cycle_limit: t.Optional[float] = opt_field()
490
- window: t.Optional[int] = opt_field()
491
- step: t.Optional[int] = opt_field()
492
- flexible_solar: t.Optional[bool] = opt_field()
493
- dart: t.Optional[bool] = opt_field()
494
- dam_annual_cycle_limit: t.Optional[float] = opt_field()
495
- no_virtual_trades: t.Optional[bool] = opt_field()
496
- initial_soe: t.Optional[float] = 0
497
- symmetric_reg: t.Optional[bool] = False
498
- duration_requirement_on_discharge: t.Optional[bool] = opt_field()
499
- solver: t.Optional[bool] = opt_field()
500
-
501
-
502
- @dataclass_json
503
- @dataclass
504
- class AncillaryEnergyPrices(object):
505
- dam: t.List[float]
506
- rtm: t.List[float]
507
-
508
-
509
- @dataclass_json
510
- @dataclass
511
- class StorageModel(object):
512
- storage_inputs: StorageInputs
513
- energy_prices: t.Union[AncillaryEnergyPrices, t.List[float]]
514
-
515
-
516
- @dataclass_json
517
- @dataclass
518
- class Utilization(object):
519
- actual: float
520
- lower: float
521
- upper: float
522
-
523
-
524
- @dataclass_json
525
- @dataclass
526
- class TimeSeriesUtilization(object):
527
- actual: t.List[float]
528
- lower: t.List[float]
529
- upper: t.List[float]
530
-
531
-
532
- @dataclass_json
533
- @dataclass
534
- class ReserveMarket(object):
535
- price: t.List[float]
536
- offer_cap: float
537
- utilization: t.Union[Utilization, TimeSeriesUtilization]
538
- obligation: t.Optional[t.List[float]] = opt_field()
539
- duration_requirement: t.Optional[float] = opt_field()
540
-
541
-
542
- @dataclass_json
543
- @dataclass
544
- class AncillaryUpMarkets(object):
545
- reserves: t.Optional[ReserveMarket] = opt_field()
546
- reg_up: t.Optional[ReserveMarket] = opt_field()
547
- generic_up: t.Optional[ReserveMarket] = opt_field()
548
-
549
-
550
- @dataclass_json
551
- @dataclass
552
- class AncillaryDownMarket(object):
553
- reg_down: t.Optional[ReserveMarket] = opt_field()
554
- generic_down: t.Optional[ReserveMarket] = opt_field()
555
-
556
-
557
- @dataclass_json
558
- @dataclass
559
- class AncillaryMarkets(object):
560
- up: t.Optional[AncillaryUpMarkets] = opt_field()
561
- down: t.Optional[AncillaryDownMarket] = opt_field()
562
-
563
-
564
- @dataclass_json
565
- @dataclass
566
- class PeakWindow(object):
567
- mask: t.List[bool]
568
- price: float
569
-
570
-
571
- @dataclass_json
572
- @dataclass
573
- class LoadPeakReduction(object):
574
- load: t.List[float]
575
- max_load: t.List[float]
576
- seasonal_peak_windows: t.Optional[t.List[PeakWindow]] = opt_field()
577
- daily_peak_windows: t.Optional[t.List[PeakWindow]] = opt_field()
578
-
579
-
580
- @dataclass_json
581
- @dataclass
582
- class StandaloneStorageModel(StorageModel):
583
- time_interval_mins: t.Optional[int] = opt_field()
584
- ancillary_markets: t.Optional[AncillaryMarkets] = opt_field()
585
- ambient_temp: t.Optional[t.List[float]] = opt_field()
586
- import_limit: t.Optional[t.List[float]] = opt_field()
587
- export_limit: t.Optional[t.List[float]] = opt_field()
588
- load_peak_reduction: t.Optional[LoadPeakReduction] = opt_field()
589
- project_term: t.Optional[int] = opt_field()
590
- project_term_units: t.Optional[str] = opt_field()
591
-
592
-
593
- @string_enum
594
- class StorageCoupling(Enum):
595
- ac = "ac"
596
- dc = "dc"
597
- hv_ac = "hv_ac"
598
-
599
-
600
- @dataclass_json
601
- @dataclass
602
- class PVStorageModel(object):
603
- storage_coupling: StorageCoupling = field(metadata=StorageCoupling.__metadata__)
604
- pv_inputs: GenerationModel
605
- storage_inputs: StorageInputs
606
- energy_prices: t.Union[AncillaryEnergyPrices, t.List[float]]
607
- enable_grid_charge_year: t.Optional[int] = opt_field()
608
- ancillary_markets: t.Optional[AncillaryMarkets] = opt_field()
609
- import_limit: t.Optional[t.List[float]] = opt_field()
610
- export_limit: t.Optional[t.List[float]] = opt_field()
611
- load_peak_reduction: t.Optional[LoadPeakReduction] = opt_field()
612
- time_interval_mins: t.Optional[int] = opt_field()
613
-
614
-
615
- @string_enum
616
- class Market:
617
- RT = "realtime"
618
- DA = "dayahead"
619
-
620
-
621
- @string_enum
622
- class AncillaryService:
623
- REGULATION_UP = "Regulation Up"
624
- REGULATION_DOWN = "Regulation Down"
625
- RESERVES = "Reserves"
626
- ECRS = "ECRS"
@@ -1,123 +0,0 @@
1
- from __future__ import annotations
2
-
3
- from dataclasses import dataclass
4
-
5
- import pandas as pd
6
- import requests
7
- from generation_models import SolarResource, SolarResourceTimeSeries
8
- from requests.exceptions import HTTPError
9
- import typing as t
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)
17
-
18
-
19
- @dataclass
20
- class PSMClient:
21
- api_key: str
22
- email: str
23
-
24
- def _get_solar_resource(
25
- self,
26
- source: str,
27
- latitude: float,
28
- longitude: float,
29
- year: t.Union[str, int],
30
- utc: bool,
31
- ):
32
- resp = requests.get(
33
- url=f"https://developer.nrel.gov/api/nsrdb/v2/solar/{source}.csv",
34
- params={
35
- "api_key": self.api_key,
36
- "email": self.email,
37
- "wkt": f"POINT({longitude} {latitude})",
38
- "names": year,
39
- "utc": "true" if utc else "false",
40
- },
41
- )
42
- if resp.status_code == 400:
43
- raise HTTPError(resp.json()["errors"])
44
- resp.raise_for_status()
45
- return resp.text
46
-
47
- @staticmethod
48
- def _process_csv(raw: str) -> SolarResource:
49
- with StringIO(raw) as f:
50
- _meta = [f.readline().split(",") for _ in range(2)]
51
- _data = pd.read_csv(f)
52
- meta = {k: v for k, v in zip(*_meta)}
53
- data = _data.rename(columns=psm_column_map)
54
- return SolarResource(
55
- latitude=float(meta["Latitude"]),
56
- longitude=float(meta["Longitude"]),
57
- elevation=float(meta["Elevation"]),
58
- time_zone_offset=float(meta["Time Zone"]),
59
- data=SolarResourceTimeSeries(**data.to_dict(orient="list")),
60
- )
61
-
62
- def get_historical(
63
- self,
64
- latitude: float,
65
- longitude: float,
66
- year: int,
67
- utc: bool = False,
68
- ) -> SolarResource:
69
- raw = self._get_solar_resource(source="psm3-2-2-download", latitude=latitude, longitude=longitude, year=year, utc=utc)
70
- return self._process_csv(raw)
71
-
72
- def get_typical(
73
- self,
74
- latitude: float,
75
- longitude: float,
76
- year: str = "tgy-2022",
77
- utc: bool = False,
78
- ) -> SolarResource:
79
- raw = self._get_solar_resource(source="psm3-2-2-tmy-download", latitude=latitude, longitude=longitude, year=year, utc=utc)
80
- return self._process_csv(raw)
81
-
82
-
83
- def solar_resource_from_psm_csv(
84
- filename: str,
85
- monthly_albedo: t.Optional[t.List[float]] = None,
86
- ) -> SolarResource:
87
- """_"""
88
- with open(filename) as f:
89
- _meta = [f.readline().split(",") for _ in range(2)]
90
- _data = pd.read_csv(f)
91
- meta = {k: v for k, v in zip(*_meta)}
92
- data = _data.rename(columns=psm_column_map)
93
- return SolarResource(
94
- latitude=float(meta["Latitude"]),
95
- longitude=float(meta["Longitude"]),
96
- elevation=float(meta["Elevation"]),
97
- time_zone_offset=float(meta["Time Zone"]),
98
- data=SolarResourceTimeSeries(**data.to_dict(orient="list")),
99
- monthly_albedo=monthly_albedo,
100
- )
101
-
102
-
103
- psm_column_map = {
104
- "Year": "year",
105
- "Month": "month",
106
- "Day": "day",
107
- "Hour": "hour",
108
- "Minute": "minute",
109
- "GHI": "gh",
110
- "DNI": "dn",
111
- "DHI": "df",
112
- "POA": "poa",
113
- "Temperature": "tdry",
114
- # twet
115
- "Dew Point": "tdew",
116
- "Relative Humidity": "rhum",
117
- "Pressure": "pres",
118
- # Snow
119
- "Surface Albedo": "alb",
120
- # aod
121
- "Wind Speed": "wspd",
122
- "Wind Direction": "wdir",
123
- }
@@ -1,12 +0,0 @@
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=j03o3hMffnUttRzxqiGx3qUonCplbI6UENK2W-dz1vY,5390
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.18.dist-info/LICENSE,sha256=LbMfEdjEK-IRzvCfdEBhn9UCANze0Rc7hWrQTEj_xvU,1079
10
- tyba_client-0.4.18.dist-info/METADATA,sha256=GW_akUmZaFqFqM6bajA_0GXOFf2LFcPzho7m1It-A60,1324
11
- tyba_client-0.4.18.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
12
- tyba_client-0.4.18.dist-info/RECORD,,