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.
Files changed (62) hide show
  1. pipeline/__init__.py +4 -0
  2. pipeline/__main__.py +1 -0
  3. pipeline/api/__init__.py +0 -0
  4. pipeline/api/eds.py +980 -0
  5. pipeline/api/rjn.py +157 -0
  6. pipeline/api/status_api.py +9 -0
  7. pipeline/calls.py +108 -0
  8. pipeline/cli.py +282 -0
  9. pipeline/configrationmanager.py +22 -0
  10. pipeline/decorators.py +13 -0
  11. pipeline/env.py +61 -0
  12. pipeline/environment.py +59 -0
  13. pipeline/gui_fastapi_plotly_live.py +78 -0
  14. pipeline/gui_mpl_live.py +113 -0
  15. pipeline/helpers.py +125 -0
  16. pipeline/logging_setup.py +45 -0
  17. pipeline/pastehelpers.py +10 -0
  18. pipeline/philosophy.py +62 -0
  19. pipeline/plotbuffer.py +21 -0
  20. pipeline/points_loader.py +19 -0
  21. pipeline/queriesmanager.py +122 -0
  22. pipeline/time_manager.py +211 -0
  23. pipeline/workspace_manager.py +253 -0
  24. pipeline_eds-0.2.4.dist-info/LICENSE +14 -0
  25. pipeline_eds-0.2.4.dist-info/METADATA +238 -0
  26. pipeline_eds-0.2.4.dist-info/RECORD +62 -0
  27. pipeline_eds-0.2.4.dist-info/WHEEL +4 -0
  28. pipeline_eds-0.2.4.dist-info/entry_points.txt +6 -0
  29. workspaces/default-workspace.toml +3 -0
  30. workspaces/eds_to_rjn/__init__.py +0 -0
  31. workspaces/eds_to_rjn/code/__init__.py +0 -0
  32. workspaces/eds_to_rjn/code/aggregator.py +84 -0
  33. workspaces/eds_to_rjn/code/collector.py +60 -0
  34. workspaces/eds_to_rjn/code/sanitizer.py +40 -0
  35. workspaces/eds_to_rjn/code/storage.py +16 -0
  36. workspaces/eds_to_rjn/configurations/config_time.toml +11 -0
  37. workspaces/eds_to_rjn/configurations/configuration.toml +2 -0
  38. workspaces/eds_to_rjn/exports/README.md +7 -0
  39. workspaces/eds_to_rjn/exports/aggregate/README.md +7 -0
  40. workspaces/eds_to_rjn/exports/aggregate/live_data - Copy.csv +355 -0
  41. workspaces/eds_to_rjn/exports/aggregate/live_data_EFF.csv +17521 -0
  42. workspaces/eds_to_rjn/exports/aggregate/live_data_INF.csv +17521 -0
  43. workspaces/eds_to_rjn/exports/export_eds_points_neo.txt +11015 -0
  44. workspaces/eds_to_rjn/exports/manual_data_load_to_postman_wetwell.csv +8759 -0
  45. workspaces/eds_to_rjn/exports/manual_data_load_to_postman_wetwell.xlsx +0 -0
  46. workspaces/eds_to_rjn/exports/manual_effluent.csv +8759 -0
  47. workspaces/eds_to_rjn/exports/manual_influent.csv +8759 -0
  48. workspaces/eds_to_rjn/exports/manual_wetwell.csv +8761 -0
  49. workspaces/eds_to_rjn/history/time_sample.txt +0 -0
  50. workspaces/eds_to_rjn/imports/zdMaxson_idcsD321E_sid11003.toml +14 -0
  51. workspaces/eds_to_rjn/imports/zdMaxson_idcsFI8001_sid8528.toml +14 -0
  52. workspaces/eds_to_rjn/imports/zdMaxson_idcsM100FI_sid2308.toml +14 -0
  53. workspaces/eds_to_rjn/imports/zdMaxson_idcsM310LI_sid2382.toml +14 -0
  54. workspaces/eds_to_rjn/queries/default-queries.toml +4 -0
  55. workspaces/eds_to_rjn/queries/points-maxson.csv +4 -0
  56. workspaces/eds_to_rjn/queries/points-stiles.csv +4 -0
  57. workspaces/eds_to_rjn/queries/timestamps_success.json +20 -0
  58. workspaces/eds_to_rjn/scripts/__init__.py +0 -0
  59. workspaces/eds_to_rjn/scripts/daemon_runner.py +212 -0
  60. workspaces/eds_to_rjn/secrets/README.md +24 -0
  61. workspaces/eds_to_rjn/secrets/secrets-example.yaml +15 -0
  62. 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,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: poetry-core 2.1.3
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,6 @@
1
+ [console_scripts]
2
+ pipeline=pipeline.cli:app
3
+
4
+ [scripts]
5
+ controller=pipeline.daemon.controller:main_cli
6
+
@@ -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,11 @@
1
+ [rangetime.start]
2
+ year = 2024
3
+ month = 12
4
+ day = 16
5
+ hour = 15
6
+
7
+ [rangetime.end]
8
+ year = 2024
9
+ month = 12
10
+ day = 16
11
+ hour = 18
@@ -0,0 +1,2 @@
1
+ [settings]
2
+ timezone = # A valid IANA time zone string, e.g. 'America/Chicago'.
@@ -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.