luminus-py 0.2.1__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.
- luminus_py-0.2.1/PKG-INFO +196 -0
- luminus_py-0.2.1/README.md +165 -0
- luminus_py-0.2.1/luminus/__init__.py +29 -0
- luminus_py-0.2.1/luminus/client.py +512 -0
- luminus_py-0.2.1/luminus/exceptions.py +26 -0
- luminus_py-0.2.1/luminus/models.py +206 -0
- luminus_py-0.2.1/luminus/result.py +171 -0
- luminus_py-0.2.1/luminus_py.egg-info/PKG-INFO +196 -0
- luminus_py-0.2.1/luminus_py.egg-info/SOURCES.txt +15 -0
- luminus_py-0.2.1/luminus_py.egg-info/dependency_links.txt +1 -0
- luminus_py-0.2.1/luminus_py.egg-info/requires.txt +10 -0
- luminus_py-0.2.1/luminus_py.egg-info/top_level.txt +1 -0
- luminus_py-0.2.1/pyproject.toml +40 -0
- luminus_py-0.2.1/setup.cfg +4 -0
- luminus_py-0.2.1/tests/test_client.py +204 -0
- luminus_py-0.2.1/tests/test_geo.py +49 -0
- luminus_py-0.2.1/tests/test_results.py +61 -0
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: luminus-py
|
|
3
|
+
Version: 0.2.1
|
|
4
|
+
Summary: Notebook-friendly Python client for luminus-mcp
|
|
5
|
+
Author: Keith So
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/kitfunso/luminus
|
|
8
|
+
Project-URL: Repository, https://github.com/kitfunso/luminus
|
|
9
|
+
Project-URL: Issues, https://github.com/kitfunso/luminus/issues
|
|
10
|
+
Project-URL: Changelog, https://github.com/kitfunso/luminus/blob/master/CHANGELOG.md
|
|
11
|
+
Keywords: mcp,jupyter,notebook,energy,electricity,gis
|
|
12
|
+
Classifier: Development Status :: 4 - Beta
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: Intended Audience :: Financial and Insurance Industry
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
20
|
+
Classifier: Topic :: Scientific/Engineering
|
|
21
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
22
|
+
Requires-Python: >=3.10
|
|
23
|
+
Description-Content-Type: text/markdown
|
|
24
|
+
Provides-Extra: notebook
|
|
25
|
+
Requires-Dist: pandas>=2.0; extra == "notebook"
|
|
26
|
+
Provides-Extra: gis
|
|
27
|
+
Requires-Dist: geopandas>=0.14; extra == "gis"
|
|
28
|
+
Provides-Extra: all
|
|
29
|
+
Requires-Dist: pandas>=2.0; extra == "all"
|
|
30
|
+
Requires-Dist: geopandas>=0.14; extra == "all"
|
|
31
|
+
|
|
32
|
+
# luminus-py
|
|
33
|
+
|
|
34
|
+
A notebook-friendly Python client for `luminus-mcp`.
|
|
35
|
+
|
|
36
|
+
This package starts the existing Node MCP server under the hood, calls tools over stdio, and returns Python-native result objects with optional pandas helpers.
|
|
37
|
+
|
|
38
|
+
Any MCP tool exposed by `luminus-mcp` is callable directly as a Python method, so the SDK does not need a hand-written wrapper for every tool.
|
|
39
|
+
|
|
40
|
+
The SDK also includes geospatial helpers for notebook workflows: `to_geojson()` for lightweight mapping and `to_geodataframe()` for GeoPandas users.
|
|
41
|
+
|
|
42
|
+
Roadmap: see [`../docs/python-sdk-roadmap.md`](../docs/python-sdk-roadmap.md).
|
|
43
|
+
|
|
44
|
+
## Install
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
pip install luminus-py[notebook]
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
For GIS notebook work:
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
pip install luminus-py[all]
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
You also need `luminus-mcp` itself available on your machine, because the Python SDK starts the existing Node MCP server under the hood.
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
npm install -g luminus-mcp@0.2.0
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## API keys
|
|
63
|
+
|
|
64
|
+
Keyed tools use the same auth model as the Node server. Resolution order is:
|
|
65
|
+
1. environment variables like `ENTSOE_API_KEY`
|
|
66
|
+
2. `~/.luminus/keys.json`
|
|
67
|
+
|
|
68
|
+
Example `~/.luminus/keys.json`:
|
|
69
|
+
|
|
70
|
+
```json
|
|
71
|
+
{
|
|
72
|
+
"ENTSOE_API_KEY": "...",
|
|
73
|
+
"GIE_API_KEY": "...",
|
|
74
|
+
"FINGRID_API_KEY": "..."
|
|
75
|
+
}
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
Per-notebook overrides are also supported:
|
|
79
|
+
|
|
80
|
+
```python
|
|
81
|
+
lum = Luminus(profile="trader", env={"ENTSOE_API_KEY": "..."})
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
## Quick start
|
|
85
|
+
|
|
86
|
+
```python
|
|
87
|
+
from luminus import Luminus
|
|
88
|
+
|
|
89
|
+
with Luminus(profile="trader") as lum:
|
|
90
|
+
prices = lum.get_day_ahead_prices(zone="DE")
|
|
91
|
+
df = prices.to_pandas()
|
|
92
|
+
|
|
93
|
+
# Any MCP tool can be called directly
|
|
94
|
+
flows = lum.get_cross_border_flows(from_zone="DE", to_zone="NL")
|
|
95
|
+
site = lum.compare_sites(sites=[
|
|
96
|
+
{"name": "A", "lat": 52.1, "lon": 0.1},
|
|
97
|
+
{"name": "B", "lat": 52.2, "lon": 0.2},
|
|
98
|
+
], country="GB")
|
|
99
|
+
|
|
100
|
+
# GIS-friendly exports
|
|
101
|
+
geojson = site.to_geojson(data_key="rankings")
|
|
102
|
+
|
|
103
|
+
# Batch several calls into one DataFrame
|
|
104
|
+
multi_zone = lum.call_many_to_pandas(
|
|
105
|
+
"get_day_ahead_prices",
|
|
106
|
+
[{"zone": "DE"}, {"zone": "FR"}, {"zone": "NL"}],
|
|
107
|
+
parallel=True,
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
# One-shot export helpers
|
|
111
|
+
prices_df = lum.call_tool_to_pandas("get_day_ahead_prices", {"zone": "DE"})
|
|
112
|
+
rankings_geojson = lum.call_tool_to_geojson("compare_sites", {
|
|
113
|
+
"country": "GB",
|
|
114
|
+
"sites": [
|
|
115
|
+
{"label": "A", "lat": 52.1, "lon": 0.1},
|
|
116
|
+
{"label": "B", "lat": 52.2, "lon": 0.2},
|
|
117
|
+
],
|
|
118
|
+
}, data_key="rankings")
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
## Notebook demos
|
|
122
|
+
|
|
123
|
+
Polished notebook demos live in [`examples/`](examples/):
|
|
124
|
+
|
|
125
|
+
- [Trader workflow](examples/trader_workflow.ipynb)
|
|
126
|
+
- [GIS siting workflow](examples/gis_siting_workflow.ipynb)
|
|
127
|
+
- [BESS shortlist workflow](examples/bess_shortlist_workflow.ipynb)
|
|
128
|
+
|
|
129
|
+
## Notebook-first helpers
|
|
130
|
+
|
|
131
|
+
The Python SDK now ships a few opinionated helpers for high-usage analyst flows:
|
|
132
|
+
|
|
133
|
+
- `lum.get_outages_frame(...)`
|
|
134
|
+
- `lum.get_cross_border_flows_many([...])`
|
|
135
|
+
- `lum.get_grid_proximity_substations(...)`
|
|
136
|
+
- `lum.get_grid_proximity_lines(...)`
|
|
137
|
+
- `lum.get_grid_proximity_snapshot(...)`
|
|
138
|
+
- `lum.get_grid_connection_queue_projects(...)`
|
|
139
|
+
- `lum.get_grid_connection_queue_sites(...)`
|
|
140
|
+
- `lum.get_grid_connection_queue_snapshot(...)`
|
|
141
|
+
- `lum.estimate_site_revenue_frame(...)`
|
|
142
|
+
- `lum.estimate_site_revenue_estimate(...)`
|
|
143
|
+
|
|
144
|
+
Example:
|
|
145
|
+
|
|
146
|
+
```python
|
|
147
|
+
from luminus import GridProximitySnapshot, Luminus, SiteRevenueEstimate
|
|
148
|
+
|
|
149
|
+
with Luminus(profile="gis") as lum:
|
|
150
|
+
outages = lum.get_outages_frame(zone="DE", type="generation")
|
|
151
|
+
flows = lum.get_cross_border_flows_many([("DE", "NL"), ("FR", "DE")])
|
|
152
|
+
substations = lum.get_grid_proximity_substations(lat=52.0, lon=0.1)
|
|
153
|
+
queue = lum.get_grid_connection_queue_projects(connection_site_query="Berkswell")
|
|
154
|
+
revenue = lum.estimate_site_revenue_frame(
|
|
155
|
+
lat=52.0,
|
|
156
|
+
lon=0.1,
|
|
157
|
+
zone="GB",
|
|
158
|
+
technology="bess",
|
|
159
|
+
capacity_mw=20,
|
|
160
|
+
)
|
|
161
|
+
|
|
162
|
+
proximity: GridProximitySnapshot = lum.get_grid_proximity_snapshot(lat=52.0, lon=0.1)
|
|
163
|
+
estimate: SiteRevenueEstimate = lum.estimate_site_revenue_estimate(
|
|
164
|
+
lat=52.0,
|
|
165
|
+
lon=0.1,
|
|
166
|
+
zone="GB",
|
|
167
|
+
technology="bess",
|
|
168
|
+
)
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
## Errors and typed models
|
|
172
|
+
|
|
173
|
+
- Startup failures now raise `LuminusStartupError`.
|
|
174
|
+
- Tool-side configuration failures raise `LuminusConfigurationError`.
|
|
175
|
+
- Tool-side upstream/data-source failures raise `LuminusUpstreamError`.
|
|
176
|
+
- Dynamic whole-surface access still works through `LuminusResult`, and common GIS/revenue flows also expose opt-in typed models.
|
|
177
|
+
|
|
178
|
+
## Notes
|
|
179
|
+
|
|
180
|
+
- Use `lum.list_tools()` to see the live tool surface for the active profile.
|
|
181
|
+
- Use `lum.describe_tool("tool_name")` to inspect the MCP description/schema metadata.
|
|
182
|
+
- Use `lum.call_many()` / `lum.call_many_to_pandas()` for generic multi-zone or multi-site notebook pulls.
|
|
183
|
+
- Use `parallel=True` on batch helpers when you want the SDK to fan out across multiple MCP subprocesses.
|
|
184
|
+
- Use `lum.get_day_ahead_prices_many()` and `lum.get_generation_mix_many()` for common analyst workflows.
|
|
185
|
+
- Use `lum.compare_sites_rankings()` together with `lum.compare_sites_rankings_geojson()` and `lum.compare_sites_rankings_geodataframe()` for ranked siting output.
|
|
186
|
+
- Use the typed snapshots only when they help notebook readability; the raw dynamic MCP surface is still available.
|
|
187
|
+
- Notebook demos live in [`examples/`](examples/).
|
|
188
|
+
- Use `to_geojson()` for lightweight mapping and `to_geodataframe()` when GeoPandas is installed.
|
|
189
|
+
|
|
190
|
+
- Requires `luminus-mcp` to be available on `PATH`, unless you pass an explicit command.
|
|
191
|
+
- By default the client starts `luminus-mcp --profile <profile>`.
|
|
192
|
+
- For local repo development you can point it at the built server directly:
|
|
193
|
+
|
|
194
|
+
```python
|
|
195
|
+
lum = Luminus(command=["node", r"C:\Users\skf_s\luminus\dist\index.js"], profile="gis")
|
|
196
|
+
```
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
# luminus-py
|
|
2
|
+
|
|
3
|
+
A notebook-friendly Python client for `luminus-mcp`.
|
|
4
|
+
|
|
5
|
+
This package starts the existing Node MCP server under the hood, calls tools over stdio, and returns Python-native result objects with optional pandas helpers.
|
|
6
|
+
|
|
7
|
+
Any MCP tool exposed by `luminus-mcp` is callable directly as a Python method, so the SDK does not need a hand-written wrapper for every tool.
|
|
8
|
+
|
|
9
|
+
The SDK also includes geospatial helpers for notebook workflows: `to_geojson()` for lightweight mapping and `to_geodataframe()` for GeoPandas users.
|
|
10
|
+
|
|
11
|
+
Roadmap: see [`../docs/python-sdk-roadmap.md`](../docs/python-sdk-roadmap.md).
|
|
12
|
+
|
|
13
|
+
## Install
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
pip install luminus-py[notebook]
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
For GIS notebook work:
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
pip install luminus-py[all]
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
You also need `luminus-mcp` itself available on your machine, because the Python SDK starts the existing Node MCP server under the hood.
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
npm install -g luminus-mcp@0.2.0
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## API keys
|
|
32
|
+
|
|
33
|
+
Keyed tools use the same auth model as the Node server. Resolution order is:
|
|
34
|
+
1. environment variables like `ENTSOE_API_KEY`
|
|
35
|
+
2. `~/.luminus/keys.json`
|
|
36
|
+
|
|
37
|
+
Example `~/.luminus/keys.json`:
|
|
38
|
+
|
|
39
|
+
```json
|
|
40
|
+
{
|
|
41
|
+
"ENTSOE_API_KEY": "...",
|
|
42
|
+
"GIE_API_KEY": "...",
|
|
43
|
+
"FINGRID_API_KEY": "..."
|
|
44
|
+
}
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Per-notebook overrides are also supported:
|
|
48
|
+
|
|
49
|
+
```python
|
|
50
|
+
lum = Luminus(profile="trader", env={"ENTSOE_API_KEY": "..."})
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## Quick start
|
|
54
|
+
|
|
55
|
+
```python
|
|
56
|
+
from luminus import Luminus
|
|
57
|
+
|
|
58
|
+
with Luminus(profile="trader") as lum:
|
|
59
|
+
prices = lum.get_day_ahead_prices(zone="DE")
|
|
60
|
+
df = prices.to_pandas()
|
|
61
|
+
|
|
62
|
+
# Any MCP tool can be called directly
|
|
63
|
+
flows = lum.get_cross_border_flows(from_zone="DE", to_zone="NL")
|
|
64
|
+
site = lum.compare_sites(sites=[
|
|
65
|
+
{"name": "A", "lat": 52.1, "lon": 0.1},
|
|
66
|
+
{"name": "B", "lat": 52.2, "lon": 0.2},
|
|
67
|
+
], country="GB")
|
|
68
|
+
|
|
69
|
+
# GIS-friendly exports
|
|
70
|
+
geojson = site.to_geojson(data_key="rankings")
|
|
71
|
+
|
|
72
|
+
# Batch several calls into one DataFrame
|
|
73
|
+
multi_zone = lum.call_many_to_pandas(
|
|
74
|
+
"get_day_ahead_prices",
|
|
75
|
+
[{"zone": "DE"}, {"zone": "FR"}, {"zone": "NL"}],
|
|
76
|
+
parallel=True,
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
# One-shot export helpers
|
|
80
|
+
prices_df = lum.call_tool_to_pandas("get_day_ahead_prices", {"zone": "DE"})
|
|
81
|
+
rankings_geojson = lum.call_tool_to_geojson("compare_sites", {
|
|
82
|
+
"country": "GB",
|
|
83
|
+
"sites": [
|
|
84
|
+
{"label": "A", "lat": 52.1, "lon": 0.1},
|
|
85
|
+
{"label": "B", "lat": 52.2, "lon": 0.2},
|
|
86
|
+
],
|
|
87
|
+
}, data_key="rankings")
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
## Notebook demos
|
|
91
|
+
|
|
92
|
+
Polished notebook demos live in [`examples/`](examples/):
|
|
93
|
+
|
|
94
|
+
- [Trader workflow](examples/trader_workflow.ipynb)
|
|
95
|
+
- [GIS siting workflow](examples/gis_siting_workflow.ipynb)
|
|
96
|
+
- [BESS shortlist workflow](examples/bess_shortlist_workflow.ipynb)
|
|
97
|
+
|
|
98
|
+
## Notebook-first helpers
|
|
99
|
+
|
|
100
|
+
The Python SDK now ships a few opinionated helpers for high-usage analyst flows:
|
|
101
|
+
|
|
102
|
+
- `lum.get_outages_frame(...)`
|
|
103
|
+
- `lum.get_cross_border_flows_many([...])`
|
|
104
|
+
- `lum.get_grid_proximity_substations(...)`
|
|
105
|
+
- `lum.get_grid_proximity_lines(...)`
|
|
106
|
+
- `lum.get_grid_proximity_snapshot(...)`
|
|
107
|
+
- `lum.get_grid_connection_queue_projects(...)`
|
|
108
|
+
- `lum.get_grid_connection_queue_sites(...)`
|
|
109
|
+
- `lum.get_grid_connection_queue_snapshot(...)`
|
|
110
|
+
- `lum.estimate_site_revenue_frame(...)`
|
|
111
|
+
- `lum.estimate_site_revenue_estimate(...)`
|
|
112
|
+
|
|
113
|
+
Example:
|
|
114
|
+
|
|
115
|
+
```python
|
|
116
|
+
from luminus import GridProximitySnapshot, Luminus, SiteRevenueEstimate
|
|
117
|
+
|
|
118
|
+
with Luminus(profile="gis") as lum:
|
|
119
|
+
outages = lum.get_outages_frame(zone="DE", type="generation")
|
|
120
|
+
flows = lum.get_cross_border_flows_many([("DE", "NL"), ("FR", "DE")])
|
|
121
|
+
substations = lum.get_grid_proximity_substations(lat=52.0, lon=0.1)
|
|
122
|
+
queue = lum.get_grid_connection_queue_projects(connection_site_query="Berkswell")
|
|
123
|
+
revenue = lum.estimate_site_revenue_frame(
|
|
124
|
+
lat=52.0,
|
|
125
|
+
lon=0.1,
|
|
126
|
+
zone="GB",
|
|
127
|
+
technology="bess",
|
|
128
|
+
capacity_mw=20,
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
proximity: GridProximitySnapshot = lum.get_grid_proximity_snapshot(lat=52.0, lon=0.1)
|
|
132
|
+
estimate: SiteRevenueEstimate = lum.estimate_site_revenue_estimate(
|
|
133
|
+
lat=52.0,
|
|
134
|
+
lon=0.1,
|
|
135
|
+
zone="GB",
|
|
136
|
+
technology="bess",
|
|
137
|
+
)
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
## Errors and typed models
|
|
141
|
+
|
|
142
|
+
- Startup failures now raise `LuminusStartupError`.
|
|
143
|
+
- Tool-side configuration failures raise `LuminusConfigurationError`.
|
|
144
|
+
- Tool-side upstream/data-source failures raise `LuminusUpstreamError`.
|
|
145
|
+
- Dynamic whole-surface access still works through `LuminusResult`, and common GIS/revenue flows also expose opt-in typed models.
|
|
146
|
+
|
|
147
|
+
## Notes
|
|
148
|
+
|
|
149
|
+
- Use `lum.list_tools()` to see the live tool surface for the active profile.
|
|
150
|
+
- Use `lum.describe_tool("tool_name")` to inspect the MCP description/schema metadata.
|
|
151
|
+
- Use `lum.call_many()` / `lum.call_many_to_pandas()` for generic multi-zone or multi-site notebook pulls.
|
|
152
|
+
- Use `parallel=True` on batch helpers when you want the SDK to fan out across multiple MCP subprocesses.
|
|
153
|
+
- Use `lum.get_day_ahead_prices_many()` and `lum.get_generation_mix_many()` for common analyst workflows.
|
|
154
|
+
- Use `lum.compare_sites_rankings()` together with `lum.compare_sites_rankings_geojson()` and `lum.compare_sites_rankings_geodataframe()` for ranked siting output.
|
|
155
|
+
- Use the typed snapshots only when they help notebook readability; the raw dynamic MCP surface is still available.
|
|
156
|
+
- Notebook demos live in [`examples/`](examples/).
|
|
157
|
+
- Use `to_geojson()` for lightweight mapping and `to_geodataframe()` when GeoPandas is installed.
|
|
158
|
+
|
|
159
|
+
- Requires `luminus-mcp` to be available on `PATH`, unless you pass an explicit command.
|
|
160
|
+
- By default the client starts `luminus-mcp --profile <profile>`.
|
|
161
|
+
- For local repo development you can point it at the built server directly:
|
|
162
|
+
|
|
163
|
+
```python
|
|
164
|
+
lum = Luminus(command=["node", r"C:\Users\skf_s\luminus\dist\index.js"], profile="gis")
|
|
165
|
+
```
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
from .client import Luminus
|
|
2
|
+
from .exceptions import (
|
|
3
|
+
LuminusConfigurationError,
|
|
4
|
+
LuminusError,
|
|
5
|
+
LuminusProtocolError,
|
|
6
|
+
LuminusStartupError,
|
|
7
|
+
LuminusToolError,
|
|
8
|
+
LuminusTransportError,
|
|
9
|
+
LuminusUpstreamError,
|
|
10
|
+
)
|
|
11
|
+
from .models import GridConnectionQueueSnapshot, GridProximitySnapshot, SiteRevenueEstimate
|
|
12
|
+
from .result import LuminusResult
|
|
13
|
+
|
|
14
|
+
__all__ = [
|
|
15
|
+
"Luminus",
|
|
16
|
+
"GridConnectionQueueSnapshot",
|
|
17
|
+
"GridProximitySnapshot",
|
|
18
|
+
"LuminusError",
|
|
19
|
+
"LuminusConfigurationError",
|
|
20
|
+
"LuminusProtocolError",
|
|
21
|
+
"LuminusStartupError",
|
|
22
|
+
"LuminusToolError",
|
|
23
|
+
"LuminusTransportError",
|
|
24
|
+
"LuminusUpstreamError",
|
|
25
|
+
"LuminusResult",
|
|
26
|
+
"SiteRevenueEstimate",
|
|
27
|
+
]
|
|
28
|
+
|
|
29
|
+
__version__ = "0.2.1"
|