tyba-client 0.2.2__tar.gz → 0.2.4__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


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

@@ -0,0 +1,56 @@
1
+ Metadata-Version: 2.1
2
+ Name: tyba-client
3
+ Version: 0.2.4
4
+ Summary: A Python API client for the Tyba Public API
5
+ License: MIT
6
+ Author: Tyler Nisonoff
7
+ Author-email: tyler@tybaenergy.com
8
+ Requires-Python: >=3.8,<4.0
9
+ Classifier: License :: OSI Approved :: MIT License
10
+ Classifier: Programming Language :: Python :: 3
11
+ Classifier: Programming Language :: Python :: 3.8
12
+ Classifier: Programming Language :: Python :: 3.9
13
+ Classifier: Programming Language :: Python :: 3.10
14
+ Classifier: Programming Language :: Python :: 3.11
15
+ Requires-Dist: dataclasses-json (>=0.5.4,<0.6.0)
16
+ Requires-Dist: generation-models (>=0.3.0,<0.4.0)
17
+ Requires-Dist: marshmallow (>=3.12.1,<4.0.0)
18
+ Requires-Dist: pandas (>=1.3.2,<2.0.0)
19
+ Requires-Dist: requests (>=2.25.1,<3.0.0)
20
+ Requires-Dist: structlog (>=23.1.0,<24.0.0)
21
+ Requires-Dist: tyba-financial-model (>=0.1.0,<0.2.0)
22
+ Description-Content-Type: text/markdown
23
+
24
+ # Tyba API Client
25
+
26
+ ## Examples
27
+ For examples see [https://github.com/Tyba-Energy/tyba-client-notebooks](https://github.com/Tyba-Energy/tyba-client-notebooks).
28
+ The script examples in tyba-python-client/examples will be deprecated eventually.
29
+
30
+ ## Development
31
+ ### Docs
32
+ We use [`sphinx`](https://github.com/sphinx-doc/sphinx) and
33
+ [`autodoc-pydantic`](https://github.com/mansenfranzen/autodoc_pydantic) to manage the documentation for the client.
34
+ Source .rst files can be found in docs/source.
35
+
36
+ To generate/update documentation for the Tyba client, first make sure
37
+ your poetry environment includes the latest versions of all the dependency packages included in the docs. For example,
38
+ if `generation-models` was recently updated and pushed to pypi, you should run `poetry update` (or if you are concerned
39
+ about changing other packages, just `poetry add generation-models==x.x.x` where `x.x.x` is the latest version).
40
+ Then, `cd` into the docs directory and run the makefile that generates the HTML documentation
41
+ ```bash
42
+ # Assuming you are already in the tyba-python-client directory
43
+ $ cd docs
44
+ $ poetry run make html
45
+ ```
46
+ The HTML documentation can be found in docs/build/html.
47
+
48
+ This HTML documentation now needs to be uploaded to s3, so it
49
+ can be served at [https://docs.tybaenergy.com/api/](https://docs.tybaenergy.com/api/). We have a python script to do this
50
+ ```bash
51
+ poetry run python docs/upload_to_s3.py
52
+ ```
53
+
54
+
55
+
56
+
@@ -0,0 +1,32 @@
1
+ # Tyba API Client
2
+
3
+ ## Examples
4
+ For examples see [https://github.com/Tyba-Energy/tyba-client-notebooks](https://github.com/Tyba-Energy/tyba-client-notebooks).
5
+ The script examples in tyba-python-client/examples will be deprecated eventually.
6
+
7
+ ## Development
8
+ ### Docs
9
+ We use [`sphinx`](https://github.com/sphinx-doc/sphinx) and
10
+ [`autodoc-pydantic`](https://github.com/mansenfranzen/autodoc_pydantic) to manage the documentation for the client.
11
+ Source .rst files can be found in docs/source.
12
+
13
+ To generate/update documentation for the Tyba client, first make sure
14
+ your poetry environment includes the latest versions of all the dependency packages included in the docs. For example,
15
+ if `generation-models` was recently updated and pushed to pypi, you should run `poetry update` (or if you are concerned
16
+ about changing other packages, just `poetry add generation-models==x.x.x` where `x.x.x` is the latest version).
17
+ Then, `cd` into the docs directory and run the makefile that generates the HTML documentation
18
+ ```bash
19
+ # Assuming you are already in the tyba-python-client directory
20
+ $ cd docs
21
+ $ poetry run make html
22
+ ```
23
+ The HTML documentation can be found in docs/build/html.
24
+
25
+ This HTML documentation now needs to be uploaded to s3, so it
26
+ can be served at [https://docs.tybaenergy.com/api/](https://docs.tybaenergy.com/api/). We have a python script to do this
27
+ ```bash
28
+ poetry run python docs/upload_to_s3.py
29
+ ```
30
+
31
+
32
+
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "tyba-client"
3
- version = "0.2.2"
3
+ version = "0.2.4"
4
4
  description = "A Python API client for the Tyba Public API"
5
5
  authors = ["Tyler Nisonoff <tyler@tybaenergy.com>"]
6
6
  license = "MIT"
@@ -16,13 +16,17 @@ dataclasses-json = "^0.5.4"
16
16
  marshmallow = "^3.12.1"
17
17
  pandas = "^1.3.2"
18
18
  tyba-financial-model = "^0.1.0"
19
- generation-models = "^0.1.0"
19
+ generation-models = "^0.3.0"
20
20
  structlog = "^23.1.0"
21
21
 
22
- [tool.poetry.dev-dependencies]
22
+ [tool.poetry.group.dev.dependencies]
23
23
  pytest = "^5.2"
24
24
  ipython = "^7.29.0"
25
25
  ipdb = "^0.13.9"
26
+ sphinx = "^7.0.1"
27
+ autodoc-pydantic = "^1.8.0"
28
+ sphinx-material = {git = "https://github.com/bashtage/sphinx-material.git"}
29
+ boto3 = "^1.28.22"
26
30
 
27
31
  [build-system]
28
32
  requires = ["poetry-core>=1.0.0"]
@@ -13,6 +13,9 @@ logger = get_logger()
13
13
 
14
14
 
15
15
  class Ancillary(object):
16
+ """
17
+
18
+ """
16
19
  def __init__(self, prices):
17
20
  self.prices = prices
18
21
 
@@ -34,6 +37,9 @@ class Ancillary(object):
34
37
 
35
38
 
36
39
  class LMP(object):
40
+ """
41
+
42
+ """
37
43
  def __init__(self, prices):
38
44
  self.prices = prices
39
45
 
@@ -51,6 +57,9 @@ class LMP(object):
51
57
 
52
58
 
53
59
  class Services(object):
60
+ """
61
+
62
+ """
54
63
  def __init__(self, client):
55
64
  self.client = client
56
65
  self.ancillary = Ancillary(self)
@@ -64,7 +73,7 @@ class Services(object):
64
73
 
65
74
 
66
75
  class Client(object):
67
- """Tyba client class"""
76
+ """Tyba valuation client class"""
68
77
 
69
78
  DEFAULT_OPTIONS = {
70
79
  'version': '0.1'
@@ -0,0 +1,166 @@
1
+ from __future__ import annotations
2
+ from generation_models import PVModuleMermoudLejeune, ONDInverter, ONDEfficiencyCurve, ONDTemperatureDerateCurve, SolarResource, SolarResourceTimeSeries
3
+ import pandas as pd
4
+ import typing as t
5
+
6
+
7
+ def read_pvsyst_file(path: str) -> dict:
8
+ try:
9
+ with open(path, "r", encoding="utf-8-sig") as f:
10
+ blob = {}
11
+ trace = [blob]
12
+ lines = [line for line in f.readlines() if "=" in line]
13
+ indentations = [len(line) - len(line.lstrip()) for line in lines]
14
+ for indentation, line in zip(indentations, lines):
15
+ if divmod(indentation, 2)[1] != 0:
16
+ raise PVSystFileError(f"invalid indentation at {line}")
17
+ structure = [(y - x) // 2 for x, y in zip(indentations[:-1], indentations[1:])]
18
+ structure.append(0)
19
+ for line, typ in zip(lines, structure):
20
+ stripped = line.strip()
21
+ k, v = stripped.split("=")
22
+ if typ == 0:
23
+ trace[-1][k] = v
24
+ elif typ == 1:
25
+ trace[-1][k] = {"type": v, "items": {}}
26
+ trace.append(trace[-1][k]["items"])
27
+ elif typ < 0:
28
+ trace[-1][k] = v
29
+ for _ in range(-1 * typ):
30
+ trace.pop()
31
+ else:
32
+ raise PVSystFileError(f"invalid structure at {line}")
33
+ except UnicodeDecodeError:
34
+ raise PVSystFileError(f"File not utf-8 encoded. Please encode the file to utf-8 and try again")
35
+ return blob
36
+
37
+
38
+ class PVSystFileError(ValueError):
39
+ pass
40
+
41
+
42
+ def pv_module_from_pan(pan_file: str, bifacial_ground_clearance_height=1.0, bifacial_transmission_factor:float = 0.013):
43
+ pan_blob = read_pvsyst_file(pan_file)
44
+ data = pan_blob["PVObject_"]["items"]
45
+ commercial = data["PVObject_Commercial"]["items"]
46
+ if "PVObject_IAM" in data:
47
+ iam_points = [
48
+ v.split(",")
49
+ for k, v in data["PVObject_IAM"]["items"]["IAMProfile"]["items"].items()
50
+ if k.startswith("Point_")
51
+ ]
52
+ iam_angles = [float(v[0]) for v in iam_points]
53
+ iam_values = [float(v[1]) for v in iam_points]
54
+ else:
55
+ iam_angles = None
56
+ iam_values = None
57
+ return PVModuleMermoudLejeune(
58
+ bifacial="BifacialityFactor" in data,
59
+ bifacial_transmission_factor=bifacial_transmission_factor,
60
+ bifaciality=float(data.get("BifacialityFactor", 0.65)),
61
+ bifacial_ground_clearance_height=bifacial_ground_clearance_height,
62
+ n_parallel=int(data["NCelP"]),
63
+ n_diodes=int(data["NDiode"]),
64
+ n_series=int(data["NCelS"]),
65
+ t_ref=float(data["TRef"]),
66
+ s_ref=float(data["GRef"]),
67
+ i_sc_ref=float(data["Isc"]),
68
+ v_oc_ref=float(data["Voc"]),
69
+ i_mp_ref=float(data["Imp"]),
70
+ v_mp_ref=float(data["Vmp"]),
71
+ alpha_sc=float(data["muISC"]) * 1e-3, # TODO: check units
72
+ beta_oc=float(data["muVocSpec"]) * 1e-3,
73
+ n_0=float(data["Gamma"]),
74
+ mu_n=float(data["muGamma"]),
75
+ r_sh_ref=float(data["RShunt"]),
76
+ r_s=float(data["RSerie"]),
77
+ r_sh_0=float(data["Rp_0"]),
78
+ r_sh_exp=float(data["Rp_Exp"]),
79
+ tech=data["Technol"],
80
+ length=float(commercial["Height"]),
81
+ width=float(commercial["Width"]),
82
+ # faiman cell temp model used by PVSyst
83
+ t_c_fa_alpha=float(data["Absorb"]),
84
+ # IAM
85
+ iam_c_cs_iam_value=iam_values,
86
+ iam_c_cs_inc_angle=iam_angles,
87
+ custom_d2_mu_tau=data.get("D2MuTau"),
88
+ )
89
+
90
+
91
+ def inverter_from_ond(ond_file: str, includes_xfmr: bool = True):
92
+ ond = read_pvsyst_file(ond_file)
93
+ data = ond['PVObject_']['items']
94
+ converter = data['Converter']['items']
95
+ voltage_curve_points = [float(v) for v in converter['VNomEff'].split(',') if v]
96
+ if len(voltage_curve_points) != 3:
97
+ raise NotImplementedError('OND Inverter only accepts voltage curves of length 3')
98
+ temp_derate_curve = ONDTemperatureDerateCurve(
99
+ ambient_temp=[-300, float(converter['TPMax']), float(converter['TPNom']), float(converter['TPLim1']), float(converter['TPLimAbs'])],
100
+ max_ac_power=[float(converter['PMaxOUT']) * 1e3, float(converter['PMaxOUT']) * 1e3, float(converter['PNomConv']) * 1e3, float(converter['PLim1']) * 1e3, float(converter.get('PlimAbs', 0.)) * 1e3]
101
+ )
102
+ raw_power_curves = [converter[f"ProfilPIOV{i}"]['items'] for i in [1, 2, 3]]
103
+ power_curves = []
104
+ for curve in raw_power_curves:
105
+ points = [[float(v) for v in curve[f"Point_{i}"].split(',')] for i in range(1, int(curve["NPtsEff"]) + 1)]
106
+ dc, ac = zip(*points)
107
+ power_curves.append(ONDEfficiencyCurve(dc_power=dc, ac_power=ac))
108
+ aux_loss = data.get('Aux_Loss')
109
+ aux_loss_threshold = data.get('Aux_Thresh')
110
+ if aux_loss is not None:
111
+ aux_loss = float(aux_loss)
112
+ aux_loss_threshold = float(aux_loss_threshold)
113
+ return ONDInverter(
114
+ mppt_low=float(converter['VMppMin']),
115
+ mppt_high=float(converter['VMPPMax']),
116
+ paco=float(converter['PMaxOUT']) * 1e3,
117
+ vdco=voltage_curve_points[1],
118
+ temp_derate_curve=temp_derate_curve,
119
+ nominal_voltages=voltage_curve_points,
120
+ power_curves=power_curves,
121
+ dc_turn_on=float(converter['PSeuil']),
122
+ pnt=float(data['Night_Loss']),
123
+ aux_loss=aux_loss,
124
+ aux_loss_threshold=aux_loss_threshold,
125
+ includes_xfmr=includes_xfmr
126
+ )
127
+
128
+
129
+ psm_column_map = {
130
+ "Year": "year",
131
+ "Month": "month",
132
+ "Day": "day",
133
+ "Hour": "hour",
134
+ "Minute": "minute",
135
+ "GHI": "gh",
136
+ "DNI": "dn",
137
+ "DHI": "df",
138
+ "POA": "poa",
139
+ "Temperature": "tdry",
140
+ # twet
141
+ "Dew Point": "tdew",
142
+ "Relative Humidity": "rhum",
143
+ "Pressure": "pres",
144
+ # Snow
145
+ "Surface Albedo": "alb",
146
+ # aod
147
+ "Wind Speed": "wspd",
148
+ "Wind Direction": "wdir",
149
+ }
150
+
151
+
152
+ def solar_resource_from_psm_csv(filename: str, typical: bool = True, monthly_albedo: t.Optional[t.List[float]] = None) -> SolarResource:
153
+ with open(filename) as f:
154
+ _meta = [f.readline().split(",") for _ in range(2)]
155
+ _data = pd.read_csv(f)
156
+ meta = {k: v for k, v in zip(*_meta)}
157
+ data = _data.rename(columns=psm_column_map)
158
+ return SolarResource(
159
+ latitude=float(meta["Latitude"]),
160
+ longitude=float(meta["Longitude"]),
161
+ elevation=float(meta["Elevation"]),
162
+ time_zone_offset=float(meta["Time Zone"]),
163
+ data=SolarResourceTimeSeries(**data.to_dict(orient="list")),
164
+ monthly_albedo=monthly_albedo,
165
+ typical=typical
166
+ )
@@ -1,11 +1,10 @@
1
1
  from __future__ import annotations
2
2
  from dataclasses import dataclass, field
3
3
  from enum import Enum
4
- from io import StringIO
5
4
  from dataclasses_json import config, dataclass_json
6
5
  import pandas as pd
7
6
  import typing as t
8
- from .utils import read_pvsyst_file
7
+ from .io import read_pvsyst_file, psm_column_map
9
8
 
10
9
  from tyba_client.utils import string_enum
11
10
 
@@ -347,7 +346,7 @@ class SolarResource(object):
347
346
  _meta = [f.readline().split(",") for _ in range(2)]
348
347
  _data = pd.read_csv(f)
349
348
  meta = {k: v for k, v in zip(*_meta)}
350
- data = _data.rename(columns=_psm_data_map)
349
+ data = _data.rename(columns=psm_column_map)
351
350
  return cls(
352
351
  latitude=float(meta["Latitude"]),
353
352
  longitude=float(meta["Longitude"]),
@@ -357,29 +356,6 @@ class SolarResource(object):
357
356
  )
358
357
 
359
358
 
360
- _psm_data_map = {
361
- "Year": "year",
362
- "Month": "month",
363
- "Day": "day",
364
- "Hour": "hour",
365
- "Minute": "minute",
366
- "GHI": "gh",
367
- "DNI": "dn",
368
- "DHI": "df",
369
- "POA": "poa",
370
- "Temperature": "tdry",
371
- # twet
372
- "Dew Point": "tdew",
373
- "Relative Humidity": "rhum",
374
- "Pressure": "pres",
375
- # Snow
376
- "Surface Albedo": "alb",
377
- # aod
378
- "Wind Speed": "wspd",
379
- "Wind Direction": "wdir",
380
- }
381
-
382
-
383
359
  @string_enum
384
360
  class ArrayDegradationMode(str, Enum):
385
361
  linear = "linear"
@@ -0,0 +1,29 @@
1
+ from enum import Enum
2
+ import typing as t
3
+
4
+ from marshmallow import fields
5
+
6
+ T = t.TypeVar('T', bound=Enum)
7
+ def string_enum(cls: t.Type[T]) -> t.Type[T]:
8
+ """
9
+ decorator to allow Enums to be used with dataclass_json
10
+ Stolen from:
11
+ https://github.com/lidatong/dataclasses-json/issues/101#issuecomment-506418278""
12
+ """
13
+ class EnumField(fields.Field):
14
+ def _serialize(self, value, attr, obj, **kwargs):
15
+ return value.name
16
+
17
+ def _deserialize(self, value, attr, data, **kwargs):
18
+ return cls[value]
19
+ if (not hasattr(cls, '__metadata__')):
20
+ setattr(cls, '__metadata__', dict())
21
+
22
+ metadata = {
23
+ "dataclasses_json": {
24
+ "encoder": lambda v: v.name,
25
+ "decoder": lambda name: cls[name],
26
+ "mm_field": EnumField(),
27
+ }}
28
+ cls.__metadata__.update(metadata)
29
+ return cls
@@ -1,24 +0,0 @@
1
- Metadata-Version: 2.1
2
- Name: tyba-client
3
- Version: 0.2.2
4
- Summary: A Python API client for the Tyba Public API
5
- License: MIT
6
- Author: Tyler Nisonoff
7
- Author-email: tyler@tybaenergy.com
8
- Requires-Python: >=3.8,<4.0
9
- Classifier: License :: OSI Approved :: MIT License
10
- Classifier: Programming Language :: Python :: 3
11
- Classifier: Programming Language :: Python :: 3.8
12
- Classifier: Programming Language :: Python :: 3.9
13
- Classifier: Programming Language :: Python :: 3.10
14
- Classifier: Programming Language :: Python :: 3.11
15
- Requires-Dist: dataclasses-json (>=0.5.4,<0.6.0)
16
- Requires-Dist: generation-models (>=0.1.0,<0.2.0)
17
- Requires-Dist: marshmallow (>=3.12.1,<4.0.0)
18
- Requires-Dist: pandas (>=1.3.2,<2.0.0)
19
- Requires-Dist: requests (>=2.25.1,<3.0.0)
20
- Requires-Dist: structlog (>=23.1.0,<24.0.0)
21
- Requires-Dist: tyba-financial-model (>=0.1.0,<0.2.0)
22
- Description-Content-Type: text/markdown
23
-
24
- # Tyba API Client
@@ -1 +0,0 @@
1
- # Tyba API Client
@@ -1,36 +0,0 @@
1
- # -*- coding: utf-8 -*-
2
- from setuptools import setup
3
-
4
- packages = \
5
- ['tyba_client']
6
-
7
- package_data = \
8
- {'': ['*']}
9
-
10
- install_requires = \
11
- ['dataclasses-json>=0.5.4,<0.6.0',
12
- 'generation-models>=0.1.0,<0.2.0',
13
- 'marshmallow>=3.12.1,<4.0.0',
14
- 'pandas>=1.3.2,<2.0.0',
15
- 'requests>=2.25.1,<3.0.0',
16
- 'structlog>=23.1.0,<24.0.0',
17
- 'tyba-financial-model>=0.1.0,<0.2.0']
18
-
19
- setup_kwargs = {
20
- 'name': 'tyba-client',
21
- 'version': '0.2.2',
22
- 'description': 'A Python API client for the Tyba Public API',
23
- 'long_description': '# Tyba API Client',
24
- 'author': 'Tyler Nisonoff',
25
- 'author_email': 'tyler@tybaenergy.com',
26
- 'maintainer': 'None',
27
- 'maintainer_email': 'None',
28
- 'url': 'None',
29
- 'packages': packages,
30
- 'package_data': package_data,
31
- 'install_requires': install_requires,
32
- 'python_requires': '>=3.8,<4.0',
33
- }
34
-
35
-
36
- setup(**setup_kwargs)
@@ -1,61 +0,0 @@
1
- from enum import Enum
2
- import typing as t
3
-
4
- from marshmallow import fields
5
-
6
- T = t.TypeVar('T', bound=Enum)
7
- def string_enum(cls: t.Type[T]) -> t.Type[T]:
8
- """
9
- decorator to allow Enums to be used with dataclass_json
10
- Stolen from:
11
- https://github.com/lidatong/dataclasses-json/issues/101#issuecomment-506418278""
12
- """
13
- class EnumField(fields.Field):
14
- def _serialize(self, value, attr, obj, **kwargs):
15
- return value.name
16
-
17
- def _deserialize(self, value, attr, data, **kwargs):
18
- return cls[value]
19
- if (not hasattr(cls, '__metadata__')):
20
- setattr(cls, '__metadata__', dict())
21
-
22
- metadata = {
23
- "dataclasses_json": {
24
- "encoder": lambda v: v.name,
25
- "decoder": lambda name: cls[name],
26
- "mm_field": EnumField(),
27
- }}
28
- cls.__metadata__.update(metadata)
29
- return cls
30
-
31
-
32
- class PVSystFileError(ValueError):
33
- pass
34
-
35
-
36
- def read_pvsyst_file(path: str) -> dict:
37
- with open(path, "r", encoding="utf-8-sig") as f:
38
- blob = {}
39
- trace = [blob]
40
- lines = [line for line in f.readlines() if "=" in line]
41
- indentations = [len(line) - len(line.lstrip()) for line in lines]
42
- for indentation, line in zip(indentations, lines):
43
- if divmod(indentation, 2)[1] != 0:
44
- raise PVSystFileError(f"invalid indentation at {line}")
45
- structure = [(y - x) // 2 for x, y in zip(indentations[:-1], indentations[1:])]
46
- structure.append(0)
47
- for line, typ in zip(lines, structure):
48
- stripped = line.strip()
49
- k, v = stripped.split("=")
50
- if typ == 0:
51
- trace[-1][k] = v
52
- elif typ == 1:
53
- trace[-1][k] = {"type": v, "items": {}}
54
- trace.append(trace[-1][k]["items"])
55
- elif typ < 0:
56
- trace[-1][k] = v
57
- for _ in range(-1 * typ):
58
- trace.pop()
59
- else:
60
- raise PVSystFileError(f"invalid structure at {line}")
61
- return blob
File without changes