pipeline-eds 0.2.4__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.
- pipeline/__init__.py +4 -0
- pipeline/__main__.py +1 -0
- pipeline/api/__init__.py +0 -0
- pipeline/api/eds.py +980 -0
- pipeline/api/rjn.py +157 -0
- pipeline/api/status_api.py +9 -0
- pipeline/calls.py +108 -0
- pipeline/cli.py +282 -0
- pipeline/configrationmanager.py +22 -0
- pipeline/decorators.py +13 -0
- pipeline/env.py +61 -0
- pipeline/environment.py +59 -0
- pipeline/gui_fastapi_plotly_live.py +78 -0
- pipeline/gui_mpl_live.py +113 -0
- pipeline/helpers.py +125 -0
- pipeline/logging_setup.py +45 -0
- pipeline/pastehelpers.py +10 -0
- pipeline/philosophy.py +62 -0
- pipeline/plotbuffer.py +21 -0
- pipeline/points_loader.py +19 -0
- pipeline/queriesmanager.py +122 -0
- pipeline/time_manager.py +211 -0
- pipeline/workspace_manager.py +253 -0
- pipeline_eds-0.2.4.dist-info/LICENSE +14 -0
- pipeline_eds-0.2.4.dist-info/METADATA +238 -0
- pipeline_eds-0.2.4.dist-info/RECORD +62 -0
- pipeline_eds-0.2.4.dist-info/WHEEL +4 -0
- pipeline_eds-0.2.4.dist-info/entry_points.txt +6 -0
- workspaces/default-workspace.toml +3 -0
- workspaces/eds_to_rjn/__init__.py +0 -0
- workspaces/eds_to_rjn/code/__init__.py +0 -0
- workspaces/eds_to_rjn/code/aggregator.py +84 -0
- workspaces/eds_to_rjn/code/collector.py +60 -0
- workspaces/eds_to_rjn/code/sanitizer.py +40 -0
- workspaces/eds_to_rjn/code/storage.py +16 -0
- workspaces/eds_to_rjn/configurations/config_time.toml +11 -0
- workspaces/eds_to_rjn/configurations/configuration.toml +2 -0
- workspaces/eds_to_rjn/exports/README.md +7 -0
- workspaces/eds_to_rjn/exports/aggregate/README.md +7 -0
- workspaces/eds_to_rjn/exports/aggregate/live_data - Copy.csv +355 -0
- workspaces/eds_to_rjn/exports/aggregate/live_data_EFF.csv +17521 -0
- workspaces/eds_to_rjn/exports/aggregate/live_data_INF.csv +17521 -0
- workspaces/eds_to_rjn/exports/export_eds_points_neo.txt +11015 -0
- workspaces/eds_to_rjn/exports/manual_data_load_to_postman_wetwell.csv +8759 -0
- workspaces/eds_to_rjn/exports/manual_data_load_to_postman_wetwell.xlsx +0 -0
- workspaces/eds_to_rjn/exports/manual_effluent.csv +8759 -0
- workspaces/eds_to_rjn/exports/manual_influent.csv +8759 -0
- workspaces/eds_to_rjn/exports/manual_wetwell.csv +8761 -0
- workspaces/eds_to_rjn/history/time_sample.txt +0 -0
- workspaces/eds_to_rjn/imports/zdMaxson_idcsD321E_sid11003.toml +14 -0
- workspaces/eds_to_rjn/imports/zdMaxson_idcsFI8001_sid8528.toml +14 -0
- workspaces/eds_to_rjn/imports/zdMaxson_idcsM100FI_sid2308.toml +14 -0
- workspaces/eds_to_rjn/imports/zdMaxson_idcsM310LI_sid2382.toml +14 -0
- workspaces/eds_to_rjn/queries/default-queries.toml +4 -0
- workspaces/eds_to_rjn/queries/points-maxson.csv +4 -0
- workspaces/eds_to_rjn/queries/points-stiles.csv +4 -0
- workspaces/eds_to_rjn/queries/timestamps_success.json +20 -0
- workspaces/eds_to_rjn/scripts/__init__.py +0 -0
- workspaces/eds_to_rjn/scripts/daemon_runner.py +212 -0
- workspaces/eds_to_rjn/secrets/README.md +24 -0
- workspaces/eds_to_rjn/secrets/secrets-example.yaml +15 -0
- workspaces/eds_to_termux/..txt +0 -0
@@ -0,0 +1,238 @@
|
|
1
|
+
Metadata-Version: 2.3
|
2
|
+
Name: pipeline-eds
|
3
|
+
Version: 0.2.4
|
4
|
+
Summary: The official API pipeline library for mulch-based projects. Key target: Emerson Ovation EDS REST API.
|
5
|
+
License: BSD-3
|
6
|
+
Author: George Clayton Bennett
|
7
|
+
Author-email: george.bennett@memphistn.gov
|
8
|
+
Requires-Python: >=3.11,<4.0.0
|
9
|
+
Classifier: License :: Other/Proprietary License
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
11
|
+
Classifier: Programming Language :: Python :: 3.11
|
12
|
+
Classifier: Programming Language :: Python :: 3.12
|
13
|
+
Classifier: Programming Language :: Python :: 3.13
|
14
|
+
Requires-Dist: certifi (>=2025.1.31,<2026.0.0)
|
15
|
+
Requires-Dist: fastapi (>=0.115.12,<0.116.0)
|
16
|
+
Requires-Dist: mulch (>=0.2.8,<0.3.0)
|
17
|
+
Requires-Dist: mysql-connector-python (>=9.3.0,<10.0.0)
|
18
|
+
Requires-Dist: pendulum (>=3.1.0,<4.0.0)
|
19
|
+
Requires-Dist: plotly (>=6.2.0,<7.0.0)
|
20
|
+
Requires-Dist: pyodbc (>=5.2.0,<6.0.0)
|
21
|
+
Requires-Dist: pyyaml (>=6.0.2,<7.0.0)
|
22
|
+
Requires-Dist: requests (>=2.32.3,<3.0.0)
|
23
|
+
Requires-Dist: schedule (>=1.2.2,<2.0.0)
|
24
|
+
Requires-Dist: toml (>=0.10.2,<0.11.0)
|
25
|
+
Requires-Dist: tzdata (>=2025.2,<2026.0)
|
26
|
+
Requires-Dist: urllib3 (>=2.4.0,<3.0.0)
|
27
|
+
Requires-Dist: uvicorn (>=0.34.3,<0.35.0)
|
28
|
+
Project-URL: Homepage, https://github.com/city-of-memphis-wastewater/pipeline
|
29
|
+
Project-URL: Repository, https://github.com/city-of-memphis-wastewater/pipeline
|
30
|
+
Description-Content-Type: text/markdown
|
31
|
+
|
32
|
+
# pipeline
|
33
|
+
The primary purpose of this project is to ease API access to Enterprise Data Server (EDS) machines set up by Emerson to compliment an Emerson Ovation local system.
|
34
|
+
Use-cases include data exchange with third-party contractors and also data access for in-house employees on work and personal devices.
|
35
|
+
|
36
|
+
*Scroll down to ***Rollout, setup, etc.*** to see information about dependencies, Poetry, and pyenv.*
|
37
|
+
|
38
|
+
## How to run pipeline
|
39
|
+
|
40
|
+
Check that the secrets.yaml file in the default-workspace is loading:
|
41
|
+
```
|
42
|
+
poetry run python -m src.pipeline.env
|
43
|
+
```
|
44
|
+
Recieve an export file of all the points known to your EDS:
|
45
|
+
```
|
46
|
+
poetry run python -m src.pipeline.api.eds demo-point-export
|
47
|
+
```
|
48
|
+
Run the existing eds_to_rjn workspace:
|
49
|
+
```
|
50
|
+
poetry run python -m workspaces.eds_to_rjn.scripts.daemon_runner test
|
51
|
+
```
|
52
|
+
Check connectivity:
|
53
|
+
```
|
54
|
+
poetry run python -m src.pipeline.api.eds ping
|
55
|
+
poetry run python -m src.pipeline.api.rjn ping
|
56
|
+
```
|
57
|
+
Other commands:
|
58
|
+
```
|
59
|
+
.\main.bat # put the daemon into service: ill advised.
|
60
|
+
.\main.ps1
|
61
|
+
```
|
62
|
+
|
63
|
+
## Implementation
|
64
|
+
The current ideal implementation of `pipeline` involves `Windows Task Scheduler`.
|
65
|
+
This is installed on an `EDS` server, to pull data and send it to a third party.
|
66
|
+
The Task is set up to call `main_eds_to_rjn_quiet.ps1` as the entry point.
|
67
|
+
The iterative hourly timing handled is by `Windows Task Scheduler` rather than pythonically with the (no unused) `setup_schedules()` function.
|
68
|
+
The `main` function from the `daemon_runner` file is run; this used to call the `run_hourly_tabular_trend_eds_to_rjn()` function.
|
69
|
+
Environment managemenet is handled with `venv` rather than `pyenv` and `poetry`, because `Task Scheduler` directs the process to a different user.
|
70
|
+
|
71
|
+
## secrets.yaml
|
72
|
+
Access will not work without a secrets.yaml file in /pipeline/workspaces/your-workspace-name/config/secrets.yaml
|
73
|
+
|
74
|
+
*API keys are specific to each the workspace, and can be referenced in the workspace scripts.*
|
75
|
+
You would edit the secrets.yaml file to specify your own EDS server and credentials.
|
76
|
+
Important: You need to VPN onto the same network as your server, EDS or otherwise, if it is not available to the outside world.
|
77
|
+
### Example secrets.yaml:
|
78
|
+
```
|
79
|
+
eds_apis:
|
80
|
+
MyServer1:
|
81
|
+
url: "http://127.0.0.1:43084/api/v1/"
|
82
|
+
username: "admin"
|
83
|
+
password: ""
|
84
|
+
MyServer2:
|
85
|
+
url: "http://some-ip-address:port/api/v1/"
|
86
|
+
username: "admin"
|
87
|
+
password: ""
|
88
|
+
|
89
|
+
contractor_apis:
|
90
|
+
MySpecialContractor:
|
91
|
+
url: "https://contractor-api.com/v1/special/"
|
92
|
+
client_id: "special-user"
|
93
|
+
password: "2685steam"
|
94
|
+
```
|
95
|
+
The ***workspaces*** folder is designed to accomodate any custom workspaces or projects that you might add. You can alter the default projet file by altering the directory name indicated in the /pipeline/workspaces/default-workspace.toml file.
|
96
|
+
|
97
|
+
# Future goals:
|
98
|
+
- Submit a pipeline library to PyPi.
|
99
|
+
- Generate a cookiecutter template for building new project directories.
|
100
|
+
- Use mulchcli (another ongoing repo) to guide users through setting up secrets.yaml files step-by-step.
|
101
|
+
|
102
|
+
# Maintenance Notes:
|
103
|
+
- Implement session = requests.Session(), like an adult.
|
104
|
+
- daemon_runner.py should use both Maxson and Stiles access.
|
105
|
+
|
106
|
+
# Rollout, setup, etc.
|
107
|
+
It is recommended that you use **pyenv** for setting the Python version and generating a virtual environment, though this is optional.
|
108
|
+
|
109
|
+
To benefit from the pyproject.toml rollout for this project, **Poetry** is entirely necessary for installing requirements.
|
110
|
+
|
111
|
+
### Why pyenv?
|
112
|
+
venv and virtualenv are confusing, and pyenv is not confusing. With pyenv, it is easy to run many projects at once, with different requirements for each.
|
113
|
+
You only need to install pyenv once on you system, and then you use it to access different versions of Python.
|
114
|
+
|
115
|
+
#### Note to anyone who doesn't yet understand the glory of virtual environments in Python:
|
116
|
+
Do it. You do not want to install special requirements to your system version of Python.
|
117
|
+
|
118
|
+
### Why Poetry?
|
119
|
+
**Poetry** is my favorite dependency management tool. It is very easy. You only need to install it once. **Poetry** also has the benefit of generating a directory-specific virtual environment.
|
120
|
+
|
121
|
+
## Use pyenv to set your Python version
|
122
|
+
(3.11.9 or other 3.11.xx).
|
123
|
+
### Install pyenv (skip if pyenv is already installed on your system)
|
124
|
+
How to install pyenv-win (https://github.com/pyenv-win/pyenv-win)
|
125
|
+
```
|
126
|
+
Set-ExecutionPolicy -Scope CurrentUser -ExecutionPolicy RemoteSigned
|
127
|
+
Invoke-WebRequest -UseBasicParsing -Uri "https://raw.githubusercontent.com/pyenv-win/pyenv-win/master/pyenv-win/install-pyenv-win.ps1" -OutFile "./install-pyenv-win.ps1"; &"./install-pyenv-win.ps1"
|
128
|
+
```
|
129
|
+
It is worth it to make the pyenv command persistent in PowerShell, by editing the $profile file:
|
130
|
+
```
|
131
|
+
notepad $profile
|
132
|
+
```
|
133
|
+
to include something like:
|
134
|
+
```
|
135
|
+
$env:PYENV = "$HOME\.pyenv\pyenv-win"
|
136
|
+
$env:PYENV_ROOT = "$HOME\.pyenv\pyenv-win"
|
137
|
+
$env:PYENV_HOME = "$HOME\.pyenv\pyenv-win"
|
138
|
+
$env:Path += ";$env:PYENV\bin;$env:PYENV\shims"
|
139
|
+
|
140
|
+
# Initialize pyenv
|
141
|
+
#$pyenvInit = & $env:PYENV_HOME\bin\pyenv init --path
|
142
|
+
#Invoke-Expression $pyenvInit
|
143
|
+
|
144
|
+
# Manually set up the pyenv environment
|
145
|
+
function Invoke-PyenvInit {
|
146
|
+
# Update PATH to include pyenv directories
|
147
|
+
$pyenvShims = "$env:PYENV\shims"
|
148
|
+
$pyenvBin = "$env:PYENV\bin"
|
149
|
+
$env:PATH = "$pyenvBin;$pyenvShims;$env:PATH"
|
150
|
+
}
|
151
|
+
|
152
|
+
# Initialize pyenv
|
153
|
+
Invoke-PyenvInit
|
154
|
+
```
|
155
|
+
How to install pyenv on linux (https://github.com/pyenv/pyenv)
|
156
|
+
```
|
157
|
+
curl -fsSL https://pyenv.run | bash
|
158
|
+
```
|
159
|
+
To make the pyenv command persistent in the Bourne Again SHell, edit ~/.bashrc
|
160
|
+
```
|
161
|
+
nano ~/.bashrc
|
162
|
+
```
|
163
|
+
to include something like:
|
164
|
+
```
|
165
|
+
export Path="$HOME/.local/bin:$PATH"
|
166
|
+
export PYENV_ROOT="$HOME/.pyenv"
|
167
|
+
[[ -d $PYENV_ROOT/bin ]] && export PATH="$PYENV_ROOT/bin:$PATH"
|
168
|
+
eval "$(pyenv init - bash)"
|
169
|
+
```
|
170
|
+
### Post-install, leverage the benefits of pyenv
|
171
|
+
Install Python 3.11.9 using pyenv:
|
172
|
+
```
|
173
|
+
# pyenv install --list # See all Python versions able with pyenv.
|
174
|
+
pyenv install 3.11.9
|
175
|
+
# pyenv local 3.11.9 # to set your current directory version
|
176
|
+
# pyenv global 3.11.9 # to set the assumed version in any directory
|
177
|
+
# pyenv global system # revert to your system installation as the assumed version
|
178
|
+
```
|
179
|
+
## Use Poetry to run deploy the requirements for this project
|
180
|
+
How to install poetry (https://github.com/python-poetry/poetry)
|
181
|
+
```
|
182
|
+
Remove-Item Alias:curl # Solution to a common PowerShell issue
|
183
|
+
curl -sSL https://install.python-poetry.org | python3
|
184
|
+
# Alternatively:
|
185
|
+
// pip install poetry
|
186
|
+
# Or, even:
|
187
|
+
// pyenv exec pip install poetry
|
188
|
+
```
|
189
|
+
## Git clone pipeline, open source
|
190
|
+
```
|
191
|
+
git clone https://github.com/City-of-Memphis-Wastewater/pipeline.git
|
192
|
+
cd pipline
|
193
|
+
pyenv local 3.11.9 # to set your current directory version
|
194
|
+
```
|
195
|
+
Explicitly set poetry to use the local pyenv version.
|
196
|
+
```
|
197
|
+
poetry python list
|
198
|
+
# You'll see something like this:
|
199
|
+
>> 3.13.2 CPython System C:/Users/<user>/.pyenv/pyenv-win/versions/3.13.2/python3.13.exe
|
200
|
+
>> 3.13.2 CPython System C:/Users/<user>/.pyenv/pyenv-win/versions/3.13.2/python313.exe
|
201
|
+
>> 3.13.2 CPython System C:/Users/<user>/.pyenv/pyenv-win/versions/3.13.2/python3.exe
|
202
|
+
>> 3.13.2 CPython System C:/Users/<user>/.pyenv/pyenv-win/versions/3.13.2/python.exe
|
203
|
+
>> 3.11.9 CPython System C:/Users/<user>/.pyenv/pyenv-win/versions/3.11.9/python3.11.exe
|
204
|
+
>> 3.11.9 CPython System C:/Users/<user>/.pyenv/pyenv-win/versions/3.11.9/python311.exe
|
205
|
+
>> 3.11.9 CPython System C:/Users/<user>/.pyenv/pyenv-win/versions/3.11.9/python3.exe
|
206
|
+
>> 3.11.9 CPython System C:/Users/<user>/.pyenv/pyenv-win/versions/3.11.9/python.exe
|
207
|
+
# Copy and paste ~any~ of the comparable paths to pyenv 3.11.9 ...
|
208
|
+
poetry use C:\Users\<user>\.pyenv\pyenv-win\versions\3.11.9\python.exe
|
209
|
+
```
|
210
|
+
Pull the requirements from the pyproject.toml file for packagage installation.
|
211
|
+
```
|
212
|
+
poetry install
|
213
|
+
# This is where the magic happens.
|
214
|
+
# When in doubt, run this again.
|
215
|
+
# Especially if you get a ModuleNotFound warning.
|
216
|
+
# Sometimes it doesn't take until you use "poetry run python".
|
217
|
+
poetry run python
|
218
|
+
poetry run python -m src.pipeline.env
|
219
|
+
poetry run python -m src.pipeline.api.eds ping
|
220
|
+
poetry run python -m workspaces.eds_to_rjn.scripts.main
|
221
|
+
```
|
222
|
+
|
223
|
+
# References
|
224
|
+
PEP621.toml
|
225
|
+
|
226
|
+
# Installation on Termux on Android
|
227
|
+
|
228
|
+
Due to `maturin` (Rust build dependency) required by newer versions of `pydantic`, use older versions of FastAPI and Pydantic that don't require Rust, to successfully install on Termux.
|
229
|
+
|
230
|
+
Also, Termux does not allow `poetry` or `pyenv`, so install packages directly to your generic environement with pip.
|
231
|
+
|
232
|
+
```
|
233
|
+
pip install -r requirements-termux.txt
|
234
|
+
```
|
235
|
+
|
236
|
+
Note that Termux does not allow `numpy` or `pandas`. Nor does it allow any plotting through a pop up window, like with tkinter, freesimplegui, matplotlib, etc.
|
237
|
+
|
238
|
+
|
@@ -0,0 +1,62 @@
|
|
1
|
+
pipeline/__init__.py,sha256=DAy4hBleDDk9Wen4qIOGlS03qWqdt7K7n4rUpL3cIL4,173
|
2
|
+
pipeline/__main__.py,sha256=TtbGYUm7Np5eLPCHZ3iX5QPL0rSgGJ5tXVuBEORvQIE,19
|
3
|
+
pipeline/api/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
4
|
+
pipeline/api/eds.py,sha256=U0mdrYqNyYpx2J4wRwjdjVGpqVNTnI_WEr8Y2vQW3v8,44083
|
5
|
+
pipeline/api/rjn.py,sha256=V8dLviYk1or-8bdAiEXPKBeYaZ2VgBhSRoV3HN2-ojM,7150
|
6
|
+
pipeline/api/status_api.py,sha256=tAPaWTFL-rGwLkMucI_hWoTktoldv2ubg4if3Eed1EA,206
|
7
|
+
pipeline/calls.py,sha256=GMG9hEbpTZSlMRee0QEEF5FCVX98Mf_5EKS_3MQ_l4M,4191
|
8
|
+
pipeline/cli.py,sha256=bHiWHC82hqFFIsdjewe85drNmXIV8cdrXuAYfiPgOjY,10097
|
9
|
+
pipeline/configrationmanager.py,sha256=UPqa91117jNm59vvTBE7sET1ThisqRf6B2Dhtk7x9tM,624
|
10
|
+
pipeline/decorators.py,sha256=5fIIVqxSvQFaSI4ZkqPd3yqajzDxaRhgYwlC1jD2k5A,411
|
11
|
+
pipeline/env.py,sha256=d0SufWAf_yPzLDGK44_zZcqVVFMMylTp1456ILtoslI,1926
|
12
|
+
pipeline/environment.py,sha256=nr-3rPsxjnuu6G0gri8X0AZhAI0h8thsdfbt34OWmzY,1454
|
13
|
+
pipeline/gui_fastapi_plotly_live.py,sha256=JpRtA6qbCJ9XPJkd_YKAbLKUJRwiJexULkYONbJlriA,2383
|
14
|
+
pipeline/gui_mpl_live.py,sha256=g7zFtBU81XykTnb7heHfl-t5pqWniJYoSUctGTgJuL4,3625
|
15
|
+
pipeline/helpers.py,sha256=c_LYnuJYXpqwhWJYpTfmgoHAiyqiw56oqQkuGJ3FvuU,4461
|
16
|
+
pipeline/logging_setup.py,sha256=CAqWfchscCdzJ63Wf1dC3QGRnFnQqBELxyTmPZxsxgs,1674
|
17
|
+
pipeline/pastehelpers.py,sha256=pkmtULW5Zk_-ffUDDp_VsGzruIjsNB1xAIPbb9jtofE,265
|
18
|
+
pipeline/philosophy.py,sha256=QskLEpY3uZn46oyH7rHekOXiC55JqW3raThxwxaiM5U,1919
|
19
|
+
pipeline/plotbuffer.py,sha256=jCsFbT47TdR8Sq5tjj2JdhVELjRiebLPN7O4r2LjPeY,625
|
20
|
+
pipeline/points_loader.py,sha256=4OCGLiatbP3D5hixVnYcFGThvBRYt_bf5DhNGdGw_9k,519
|
21
|
+
pipeline/queriesmanager.py,sha256=0fsniKy5F8MDE5C8fsOhMO44bW9m0mZRgfONSbn3lrs,5013
|
22
|
+
pipeline/time_manager.py,sha256=gSK430SyHvhgUWLRg_z2nBiyad01v7ByyKafB138IkU,8351
|
23
|
+
pipeline/workspace_manager.py,sha256=rrK0bUlQI8nB21SSWNVXUoC7HxHnrCbi7GAjaW2zF8M,11028
|
24
|
+
workspaces/default-workspace.toml,sha256=dI8y2l2WlEbIck6IZpbuQUP8-Bf48bBE1CKKsnVMc8w,300
|
25
|
+
workspaces/eds_to_rjn/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
26
|
+
workspaces/eds_to_rjn/code/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
27
|
+
workspaces/eds_to_rjn/code/aggregator.py,sha256=ewU5bPwNebiyX8VKuBsrqYqaxuTFE4U4Ip5OkJCyGfw,3076
|
28
|
+
workspaces/eds_to_rjn/code/collector.py,sha256=XI_1TbA--rm72GdFyLqxWyzqk8eVlVPgXYAqQxp4_FY,2646
|
29
|
+
workspaces/eds_to_rjn/code/sanitizer.py,sha256=2yKZSJYgHGrYCKjxg56XK2rlgv3aTyl8573e3qbbTbY,1673
|
30
|
+
workspaces/eds_to_rjn/code/storage.py,sha256=4bBnb6WiIPpnyhSq5Gkt6EZUabVBEKBq7LHnmUxm1yM,564
|
31
|
+
workspaces/eds_to_rjn/configurations/config_time.toml,sha256=8XkfMn78qxRkQLpCbTFySj-73snz-EtraZBt5aR5OGM,130
|
32
|
+
workspaces/eds_to_rjn/configurations/configuration.toml,sha256=mlyQ4hXhrq7QiVBuqXdE1eyM1kAZNYSCXLa0GY7D9P8,81
|
33
|
+
workspaces/eds_to_rjn/exports/aggregate/live_data - Copy.csv,sha256=MCpJHnslRjnKdxnsk0P8dkOyCK5GrWO8JG26-L4Mihw,27627
|
34
|
+
workspaces/eds_to_rjn/exports/aggregate/live_data_EFF.csv,sha256=eVEATBPeaoAiameN5jbG-okgl8IpRS4TG4U2P7T3qjA,1504130
|
35
|
+
workspaces/eds_to_rjn/exports/aggregate/live_data_INF.csv,sha256=DjisrETlE2Oq1C9qdIEMuT_MpkBTRLBQWFuIq7N9ncQ,1539205
|
36
|
+
workspaces/eds_to_rjn/exports/aggregate/README.md,sha256=L-UH5wl3lS9c095ygF1h04siOC4bAFjHujnnWf-PM4U,381
|
37
|
+
workspaces/eds_to_rjn/exports/export_eds_points_neo.txt,sha256=FU5hDecwxbKFjui2uC3ShQS7PnvV3KdDPPbkXMmTYMw,2824328
|
38
|
+
workspaces/eds_to_rjn/exports/manual_data_load_to_postman_wetwell.csv,sha256=eWlqgjR817K5h_He9lnoA01alkRF7yQGH4aixyI0X9s,283269
|
39
|
+
workspaces/eds_to_rjn/exports/manual_data_load_to_postman_wetwell.xlsx,sha256=UmpEX5hhi5jV7mhFiN3cQRsR469NmCX9lq0sgbmfrQs,29679
|
40
|
+
workspaces/eds_to_rjn/exports/manual_effluent.csv,sha256=eWlqgjR817K5h_He9lnoA01alkRF7yQGH4aixyI0X9s,283269
|
41
|
+
workspaces/eds_to_rjn/exports/manual_influent.csv,sha256=Fv9r04Bulh9aPFt6YtFdRmAiS9jBXnX01IzBJKIkZck,288003
|
42
|
+
workspaces/eds_to_rjn/exports/manual_wetwell.csv,sha256=3Sp7VgkDf5HGMzw5o7Y9oWGFsTOlbsjGwI1xzyoVN0Y,569835
|
43
|
+
workspaces/eds_to_rjn/exports/README.md,sha256=zQPg1fS7P_itarsoi-vBkU40pxoQcDFfHpAQmsbJBXI,374
|
44
|
+
workspaces/eds_to_rjn/history/time_sample.txt,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
45
|
+
workspaces/eds_to_rjn/imports/zdMaxson_idcsD321E_sid11003.toml,sha256=_oQ3CYdFmi1mRQHlQ7NXJcB_8jW9thBmFdrAosCJeOI,269
|
46
|
+
workspaces/eds_to_rjn/imports/zdMaxson_idcsFI8001_sid8528.toml,sha256=itvt2BjeEpWo5zAEFdvMRq0V8ZNIHaNFMECOmO5nGdg,275
|
47
|
+
workspaces/eds_to_rjn/imports/zdMaxson_idcsM100FI_sid2308.toml,sha256=aqeaERNMLgxjhPHLZGLlVMyQsaVJdFDUhwYM2Es0tJg,279
|
48
|
+
workspaces/eds_to_rjn/imports/zdMaxson_idcsM310LI_sid2382.toml,sha256=v1P4X05eYlMrH40HXVDwRa-VErks3ndqGS9qU_zomsM,271
|
49
|
+
workspaces/eds_to_rjn/queries/default-queries.toml,sha256=WhdFrrBWCNR2LwDwk_YkdYCkexAA7AIoiMbAhOdX3_U,264
|
50
|
+
workspaces/eds_to_rjn/queries/points-maxson.csv,sha256=fmYew1rk7TN6_8fNKk0elpiPovJJRHBxsUrb7YIaUTg,471
|
51
|
+
workspaces/eds_to_rjn/queries/points-stiles.csv,sha256=cE83N4NdxSpR54emsy8a4jXZsGTT74IxEcQ2OoZOZ00,471
|
52
|
+
workspaces/eds_to_rjn/queries/timestamps_success.json,sha256=hXBITI94lLsja-7Q-IifJLagUZZrD75isksCllGy9Qw,418
|
53
|
+
workspaces/eds_to_rjn/scripts/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
54
|
+
workspaces/eds_to_rjn/scripts/daemon_runner.py,sha256=I3ZncZR3_wSjs_uZ2CKQzpQr0OfOchwZp-v7wwK2ojs,11179
|
55
|
+
workspaces/eds_to_rjn/secrets/README.md,sha256=tWf2bhopA0C08C8ImtHNZoPde9ub-sLMjX6EMe7lyJw,600
|
56
|
+
workspaces/eds_to_rjn/secrets/secrets-example.yaml,sha256=qKGrKsKBC0ulDQRVbr1zkfNlr8WPWK4lg5GAvTqZ-T4,365
|
57
|
+
workspaces/eds_to_termux/..txt,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
58
|
+
pipeline_eds-0.2.4.dist-info/entry_points.txt,sha256=t1hTaepU6pSowdqa8OSzohoTbmDRLA3Jnp8MfqagwEs,103
|
59
|
+
pipeline_eds-0.2.4.dist-info/LICENSE,sha256=LKdx0wS1t9vFZpbRhDg_iLQ6ny-XsXRwhKAoCfrF6iA,1501
|
60
|
+
pipeline_eds-0.2.4.dist-info/METADATA,sha256=LZyy4Eoc0WKpEXbovPSNb4TEhBj4YnI6u4cr1nb7b5I,9964
|
61
|
+
pipeline_eds-0.2.4.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
|
62
|
+
pipeline_eds-0.2.4.dist-info/RECORD,,
|
@@ -0,0 +1,3 @@
|
|
1
|
+
[default-workspace]
|
2
|
+
workspace = "eds_to_rjn"
|
3
|
+
use-most-recently-edited-workspace-directory = false # while true, this will ignore the 'workspace' variable above and instead the most recently edited workpace folder. This is to prevent you from having to constantly edit this file to try new things.
|
File without changes
|
File without changes
|
@@ -0,0 +1,84 @@
|
|
1
|
+
#pipeline.aggregator.py
|
2
|
+
import csv
|
3
|
+
from collections import defaultdict
|
4
|
+
import os
|
5
|
+
from pprint import pprint
|
6
|
+
|
7
|
+
from src.pipeline.api.rjn import RjnClient #send_data_to_rjn2
|
8
|
+
from src.pipeline.time_manager import TimeManager
|
9
|
+
|
10
|
+
|
11
|
+
def aggregate_and_send(session_rjn, data_file, checkpoint_file):
|
12
|
+
|
13
|
+
# Check what has already been sent
|
14
|
+
already_sent = set()
|
15
|
+
if os.path.exists(checkpoint_file):
|
16
|
+
with open(checkpoint_file, newline='') as f:
|
17
|
+
reader = csv.reader(f)
|
18
|
+
for row in reader:
|
19
|
+
projectid, entityid, timestamp = row
|
20
|
+
already_sent.add((projectid, entityid, timestamp))
|
21
|
+
|
22
|
+
print(f"len(already_sent) = {len(already_sent)}")
|
23
|
+
|
24
|
+
# Load all available data from the live data CSV
|
25
|
+
grouped = defaultdict(list)
|
26
|
+
with open(data_file, newline='') as f:
|
27
|
+
reader = csv.DictReader(f)
|
28
|
+
for row in reader:
|
29
|
+
if row["value"] == "":
|
30
|
+
#print("Skipping empty row")
|
31
|
+
continue
|
32
|
+
elif "timestamp" not in row:
|
33
|
+
print(row.keys())
|
34
|
+
print("timestamp not in row")
|
35
|
+
continue
|
36
|
+
timestamp = row["timestamp"]
|
37
|
+
projectid = row["rjn_projectid"]
|
38
|
+
entityid = row["rjn_entityid"]
|
39
|
+
value = float(row["value"])
|
40
|
+
key = (projectid, entityid)
|
41
|
+
# Only include if not already sent
|
42
|
+
if (projectid, entityid, timestamp) not in already_sent:
|
43
|
+
grouped[key].append((timestamp, value))
|
44
|
+
|
45
|
+
print(f"len(grouped) = {len(grouped)}")
|
46
|
+
|
47
|
+
# Send data per entity
|
48
|
+
for (projectid, entityid), records in grouped.items():
|
49
|
+
print(f"projectid = {projectid}")
|
50
|
+
# Sort timestamps if needed
|
51
|
+
records.sort(key=lambda x: x[0])
|
52
|
+
|
53
|
+
timestamps = [ts for ts, _ in records]
|
54
|
+
values = [val for _, val in records]
|
55
|
+
|
56
|
+
if timestamps:
|
57
|
+
print(f"Attempting to send {len(timestamps)} values to RJN for entity {entityid} at site {projectid}")
|
58
|
+
'''
|
59
|
+
send_data_to_rjn(
|
60
|
+
base_url=rjn_base_url,
|
61
|
+
project_id=projectid,
|
62
|
+
entity_id=entityid,
|
63
|
+
headers=headers_rjn,
|
64
|
+
timestamps=timestamps,
|
65
|
+
values=values
|
66
|
+
)
|
67
|
+
'''
|
68
|
+
RjnClient.send_data_to_rjn(
|
69
|
+
session_rjn,
|
70
|
+
base_url = session_rjn.base_url,
|
71
|
+
project_id=row["rjn_projectid"],
|
72
|
+
entity_id=row["rjn_entityid"],
|
73
|
+
timestamps=timestamps,
|
74
|
+
values=[row["value"]]
|
75
|
+
)
|
76
|
+
|
77
|
+
# Record successful sends
|
78
|
+
if os.path.exists(checkpoint_file):
|
79
|
+
with open(checkpoint_file, 'a', newline='') as f:
|
80
|
+
writer = csv.writer(f)
|
81
|
+
for ts in timestamps:
|
82
|
+
writer.writerow([projectid, entityid, TimeManager(ts).as_formatted_date_time()])
|
83
|
+
else:
|
84
|
+
print(f"No new data to send for {projectid} / {entityid}")
|
@@ -0,0 +1,60 @@
|
|
1
|
+
# collector.py
|
2
|
+
from datetime import datetime
|
3
|
+
import logging
|
4
|
+
logger = logging.getLogger(__name__)
|
5
|
+
|
6
|
+
from src.pipeline.helpers import round_datetime_to_nearest_past_five_minutes
|
7
|
+
from src.pipeline.api.eds import EdsClient
|
8
|
+
|
9
|
+
|
10
|
+
def collect_live_values(session, queries_dictlist_filtered_by_session_key):
|
11
|
+
data = []
|
12
|
+
for row in queries_dictlist_filtered_by_session_key:
|
13
|
+
#print(f"\trow = {row}")
|
14
|
+
# Skip empty rows (if all values in the row are empty or None)
|
15
|
+
if not any(row.values()):
|
16
|
+
print("Skipping empty row.")
|
17
|
+
continue
|
18
|
+
|
19
|
+
# light validation - if you want to change column keys, that could be cool
|
20
|
+
#required_cols = ["iess", "rjn_projectid", "rjn_entityid"]
|
21
|
+
required_cols = ["iess"]
|
22
|
+
if any(c not in row for c in required_cols):
|
23
|
+
raise ValueError(f"Row missing required column keys: {row}")
|
24
|
+
|
25
|
+
try:
|
26
|
+
# extract and validate iess value from CSV row before it is used to retrieve data
|
27
|
+
iess = str(row["iess"]) if row["iess"] not in (None, '', '\t') else None
|
28
|
+
except KeyError as e:
|
29
|
+
print(f"Missing expected column in CSV: {e}")
|
30
|
+
continue
|
31
|
+
except ValueError as e:
|
32
|
+
print(f"Invalid data in row: {e}")
|
33
|
+
continue
|
34
|
+
|
35
|
+
try:
|
36
|
+
point_data = EdsClient.get_points_live_mod(session, iess)
|
37
|
+
if point_data is None:
|
38
|
+
print(f"No data returned for iess={iess}")
|
39
|
+
continue
|
40
|
+
conflicts = set(row.keys()) & set(point_data.keys())
|
41
|
+
if conflicts:
|
42
|
+
logger.debug(f"Warning: column key collision on {conflicts}, for iess = {iess}. This is expected.")
|
43
|
+
'''
|
44
|
+
Not the worst idea:
|
45
|
+
Use nested structures
|
46
|
+
Instead of flattening all column keys into the same dict, keep fetched data as a sub-dictionary.
|
47
|
+
In which case, the aggregate should be JSON (or TOML, whatever), not CSV.
|
48
|
+
However, we have something that works. It is fine for now.
|
49
|
+
'''
|
50
|
+
# Retrieved point data is flatly added to the existing row from the query.
|
51
|
+
row.update(point_data)
|
52
|
+
data.append(row)
|
53
|
+
except Exception as e:
|
54
|
+
print(f"Error on row: {e}")
|
55
|
+
return data
|
56
|
+
|
57
|
+
if __name__ == "__main__":
|
58
|
+
print("[x] from src.pipeline import collector")
|
59
|
+
print("[x] from src.pipeline import collector.collector_live_vales(session,query_list)")
|
60
|
+
print("[ ] from src.pipeline import collector.collector_live_vales(session,query_dict)")
|
@@ -0,0 +1,40 @@
|
|
1
|
+
'''
|
2
|
+
Title: sanitizer.py
|
3
|
+
Author: Clayton Bennett
|
4
|
+
Created: 2025 05-May 17th
|
5
|
+
|
6
|
+
Purpose:
|
7
|
+
I like it when data gathered is data returned. Sanitization should not happen during import. If not entirely probitive, the entirety of the raw should be available for unforeseen use cases.
|
8
|
+
So. We need explicit and discernible sanitization scenarios, called a scripted approach, following the preparation, collection, and aggregation, insert buzz words here, etc.
|
9
|
+
'''
|
10
|
+
from datetime import datetime
|
11
|
+
from src.pipeline.helpers import round_datetime_to_nearest_past_five_minutes
|
12
|
+
|
13
|
+
def sanitize_data_for_printing(data):
|
14
|
+
#data_sanitized_for_printing = data
|
15
|
+
#pass
|
16
|
+
#return data_sanitized_for_printing
|
17
|
+
return data
|
18
|
+
|
19
|
+
def sanitize_data_for_aggregated_storage(data):
|
20
|
+
sanitized = []
|
21
|
+
for row in data:
|
22
|
+
rounded_dt = round_datetime_to_nearest_past_five_minutes(datetime.fromtimestamp(row["ts"])) # arguably not appropriate at this point. round at transmission
|
23
|
+
#row["timestamp_sani"] = rounded_dt
|
24
|
+
#row["value_rounded"] = round(row["value"], 2)
|
25
|
+
|
26
|
+
sanitized.append({
|
27
|
+
"timestamp": rounded_dt.isoformat(),
|
28
|
+
"ts": rounded_dt.timestamp(),
|
29
|
+
"iess": row.get("iess"),
|
30
|
+
"sid": row.get("sid"),
|
31
|
+
"un": row.get("un"),
|
32
|
+
"shortdesc": row.get("shortdesc"),
|
33
|
+
"rjn_projectid": row.get("rjn_projectid"),
|
34
|
+
"rjn_entityid": row.get("rjn_entityid"),
|
35
|
+
"value": round(row.get("value"), 2)
|
36
|
+
})
|
37
|
+
|
38
|
+
data_sanitized_for_aggregated_storage = sanitized
|
39
|
+
|
40
|
+
return data_sanitized_for_aggregated_storage
|
@@ -0,0 +1,16 @@
|
|
1
|
+
import csv
|
2
|
+
from datetime import datetime
|
3
|
+
'''
|
4
|
+
Mostly defunct now that eds tabular trend is working. But still interesting.
|
5
|
+
Use:
|
6
|
+
```
|
7
|
+
storage.store_live_values(data, workspace_manager.get_aggregate_dir() / "live_data.csv")
|
8
|
+
```
|
9
|
+
'''
|
10
|
+
def store_live_values(data, path):
|
11
|
+
with open(path, 'a', newline='') as f:
|
12
|
+
writer = csv.DictWriter(f, fieldnames=data[0].keys())
|
13
|
+
if f.tell() == 0: # file is empty
|
14
|
+
writer.writeheader()
|
15
|
+
writer.writerows(data)
|
16
|
+
print(f"Live values stored, {datetime.now()} to {path}")
|
@@ -0,0 +1,7 @@
|
|
1
|
+
# Exports: export_eds_points_all.txt
|
2
|
+
Among other future possibilities, the export_eds_points_all.txt is stored here by the pipeline.api.eds demo_eds_save_point_export() function.
|
3
|
+
The point export can be called like this:
|
4
|
+
```
|
5
|
+
poetry run python -m pipeline.api.eds demo-points-export
|
6
|
+
```
|
7
|
+
The export_eds_points_all.txt file is registered in the .gitignore for security.
|
@@ -0,0 +1,7 @@
|
|
1
|
+
# Aggregate: daemon temp, live_data.csv and sent_data.csv
|
2
|
+
Files live_data.csv and sent_data.csv are stored here by workspaces.eds_to_rjn.scripts.daemon_runner main() function.
|
3
|
+
The daemon can be called like this:
|
4
|
+
```
|
5
|
+
poetry run python -m workspaces.eds_to_rjn.scripts.daemon_runner
|
6
|
+
```
|
7
|
+
The live_data.csv and sent_data.csv files are registered in the .gitignore for security.
|