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 +4 -1
- tyba_client/client.py +0 -28
- tyba_client/operations.py +3 -1
- {tyba_client-0.4.18.dist-info → tyba_client-0.5.0.dist-info}/METADATA +6 -12
- tyba_client-0.5.0.dist-info/RECORD +9 -0
- {tyba_client-0.4.18.dist-info → tyba_client-0.5.0.dist-info}/WHEEL +1 -1
- tyba_client/io.py +0 -163
- tyba_client/models.py +0 -626
- tyba_client/solar_resource.py +0 -123
- tyba_client-0.4.18.dist-info/RECORD +0 -12
- {tyba_client-0.4.18.dist-info → tyba_client-0.5.0.dist-info}/LICENSE +0 -0
tyba_client/__init__.py
CHANGED
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
|
@@ -1,27 +1,21 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: tyba-client
|
|
3
|
-
Version: 0.
|
|
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
|
+
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:
|
|
18
|
-
Requires-Dist:
|
|
19
|
-
Requires-Dist:
|
|
20
|
-
Requires-Dist:
|
|
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,,
|
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"
|
tyba_client/solar_resource.py
DELETED
|
@@ -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,,
|
|
File without changes
|