ew-energy-models 0.2.0__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.
Files changed (33) hide show
  1. ew_energy_models-0.2.0/.github/workflows/archive-forecast.yml +38 -0
  2. ew_energy_models-0.2.0/.github/workflows/publish.yml +51 -0
  3. ew_energy_models-0.2.0/.github/workflows/tests.yml +23 -0
  4. ew_energy_models-0.2.0/.gitignore +13 -0
  5. ew_energy_models-0.2.0/CITATION.cff +17 -0
  6. ew_energy_models-0.2.0/LICENSE +201 -0
  7. ew_energy_models-0.2.0/MAINTENANCE.md +119 -0
  8. ew_energy_models-0.2.0/NOTICE +8 -0
  9. ew_energy_models-0.2.0/PKG-INFO +155 -0
  10. ew_energy_models-0.2.0/README.md +129 -0
  11. ew_energy_models-0.2.0/data/.gitkeep +0 -0
  12. ew_energy_models-0.2.0/data/ieso_forecast_archive.csv +5281 -0
  13. ew_energy_models-0.2.0/examples/ontario_peak_atlas.py +105 -0
  14. ew_energy_models-0.2.0/pyproject.toml +37 -0
  15. ew_energy_models-0.2.0/scripts/archive_ieso_forecast.py +54 -0
  16. ew_energy_models-0.2.0/src/ewmodels/__init__.py +11 -0
  17. ew_energy_models-0.2.0/src/ewmodels/data/__init__.py +1 -0
  18. ew_energy_models-0.2.0/src/ewmodels/data/ieso.py +103 -0
  19. ew_energy_models-0.2.0/src/ewmodels/data/ieso_backfill.py +133 -0
  20. ew_energy_models-0.2.0/src/ewmodels/data/ieso_forecast.py +179 -0
  21. ew_energy_models-0.2.0/src/ewmodels/ontario/__init__.py +2 -0
  22. ew_energy_models-0.2.0/src/ewmodels/ontario/atlas.py +67 -0
  23. ew_energy_models-0.2.0/src/ewmodels/ontario/benchmark.py +252 -0
  24. ew_energy_models-0.2.0/src/ewmodels/ontario/ici.py +192 -0
  25. ew_energy_models-0.2.0/src/ewmodels/receipts.py +104 -0
  26. ew_energy_models-0.2.0/src/ewmodels/resolution.py +40 -0
  27. ew_energy_models-0.2.0/tests/__init__.py +0 -0
  28. ew_energy_models-0.2.0/tests/conftest.py +33 -0
  29. ew_energy_models-0.2.0/tests/fixtures/PUB_Demand_sample.csv +8 -0
  30. ew_energy_models-0.2.0/tests/test_backfill.py +61 -0
  31. ew_energy_models-0.2.0/tests/test_core.py +44 -0
  32. ew_energy_models-0.2.0/tests/test_forecast.py +66 -0
  33. ew_energy_models-0.2.0/tests/test_ontario.py +155 -0
@@ -0,0 +1,38 @@
1
+ name: archive-ieso-forecast
2
+
3
+ # Builds the open IESO forecast archive. IESO keeps every forecast version for
4
+ # only ~45 days, so this runs daily; each run back-fills anything still in
5
+ # IESO's window, then fails (and GitHub emails you) if any date is missing.
6
+ on:
7
+ schedule:
8
+ - cron: "17 14 * * *" # ~09:17 EST
9
+ - cron: "47 21 * * *" # ~16:47 EST, after the afternoon day-ahead revision
10
+ workflow_dispatch:
11
+
12
+ permissions:
13
+ contents: write
14
+
15
+ concurrency:
16
+ group: archive-forecast
17
+ cancel-in-progress: false
18
+
19
+ jobs:
20
+ archive:
21
+ runs-on: ubuntu-latest
22
+ steps:
23
+ - uses: actions/checkout@v5
24
+ - uses: actions/setup-python@v6
25
+ with:
26
+ python-version: "3.12"
27
+ - run: pip install -e .
28
+ - name: Back-fill new forecast versions
29
+ run: python scripts/archive_ieso_forecast.py --skip-check
30
+ - name: Commit
31
+ run: |
32
+ git config user.name "ew-archive-bot"
33
+ git config user.email "actions@users.noreply.github.com"
34
+ git add data/ieso_forecast_archive.csv
35
+ git diff --cached --quiet || git commit -m "data: IESO forecast archive $(date -u +%FT%H:%MZ)"
36
+ git push
37
+ - name: Gap check (fails -> email alert)
38
+ run: python scripts/archive_ieso_forecast.py --check-only
@@ -0,0 +1,51 @@
1
+ name: publish-to-pypi
2
+
3
+ # Publishes to PyPI when a GitHub Release is published. Uses PyPI Trusted
4
+ # Publishing (OIDC): no API tokens or passwords are stored anywhere.
5
+ on:
6
+ release:
7
+ types: [published]
8
+
9
+ permissions:
10
+ contents: read
11
+
12
+ jobs:
13
+ test:
14
+ runs-on: ubuntu-latest
15
+ steps:
16
+ - uses: actions/checkout@v5
17
+ - uses: actions/setup-python@v6
18
+ with:
19
+ python-version: "3.12"
20
+ - run: pip install -e ".[dev]" && pytest -q
21
+
22
+ build:
23
+ needs: test
24
+ runs-on: ubuntu-latest
25
+ steps:
26
+ - uses: actions/checkout@v5
27
+ - uses: actions/setup-python@v6
28
+ with:
29
+ python-version: "3.12"
30
+ - name: Check tag matches package version
31
+ run: |
32
+ V=$(python -c "import tomllib;print(tomllib.load(open('pyproject.toml','rb'))['project']['version'])")
33
+ [ "v$V" = "${{ github.event.release.tag_name }}" ] || { echo "Tag ${{ github.event.release.tag_name }} != v$V"; exit 1; }
34
+ - run: pip install build && python -m build
35
+ - uses: actions/upload-artifact@v4
36
+ with:
37
+ name: dist
38
+ path: dist/
39
+
40
+ publish:
41
+ needs: build
42
+ runs-on: ubuntu-latest
43
+ environment: pypi
44
+ permissions:
45
+ id-token: write # required for Trusted Publishing
46
+ steps:
47
+ - uses: actions/download-artifact@v4
48
+ with:
49
+ name: dist
50
+ path: dist/
51
+ - uses: pypa/gh-action-pypi-publish@release/v1
@@ -0,0 +1,23 @@
1
+ name: tests
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+
8
+ permissions:
9
+ contents: read
10
+
11
+ jobs:
12
+ test:
13
+ runs-on: ubuntu-latest
14
+ strategy:
15
+ matrix:
16
+ python-version: ["3.10", "3.11", "3.12", "3.13"]
17
+ steps:
18
+ - uses: actions/checkout@v5
19
+ - uses: actions/setup-python@v6
20
+ with:
21
+ python-version: ${{ matrix.python-version }}
22
+ - run: pip install -e ".[dev]"
23
+ - run: pytest -q
@@ -0,0 +1,13 @@
1
+ __pycache__/
2
+ *.py[cod]
3
+ *.egg-info/
4
+ .venv/
5
+ venv/
6
+ build/
7
+ dist/
8
+ .pytest_cache/
9
+ out/
10
+ .DS_Store
11
+ # never commit meter data
12
+ *.xml
13
+ data/private/
@@ -0,0 +1,17 @@
1
+ cff-version: 1.2.0
2
+ message: "If you use this software, please cite it as below."
3
+ title: "ew-energy-models: open, auditable energy-analytics models"
4
+ authors:
5
+ - family-names: Shah
6
+ given-names: Tirth
7
+ affiliation: Enerwyse
8
+ version: 0.2.0
9
+ date-released: 2026-10-22
10
+ license: Apache-2.0
11
+ repository-code: "https://github.com/enerwyse/ew-energy-models"
12
+ url: "https://enerwyse.ca"
13
+ keywords:
14
+ - energy analytics
15
+ - global adjustment
16
+ - industrial conservation initiative
17
+ - measurement and verification
@@ -0,0 +1,201 @@
1
+ Apache License
2
+ Version 2.0, January 2004
3
+ http://www.apache.org/licenses/
4
+
5
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6
+
7
+ 1. Definitions.
8
+
9
+ "License" shall mean the terms and conditions for use, reproduction,
10
+ and distribution as defined by Sections 1 through 9 of this document.
11
+
12
+ "Licensor" shall mean the copyright owner or entity authorized by
13
+ the copyright owner that is granting the License.
14
+
15
+ "Legal Entity" shall mean the union of the acting entity and all
16
+ other entities that control, are controlled by, or are under common
17
+ control with that entity. For the purposes of this definition,
18
+ "control" means (i) the power, direct or indirect, to cause the
19
+ direction or management of such entity, whether by contract or
20
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
21
+ outstanding shares, or (iii) beneficial ownership of such entity.
22
+
23
+ "You" (or "Your") shall mean an individual or Legal Entity
24
+ exercising permissions granted by this License.
25
+
26
+ "Source" form shall mean the preferred form for making modifications,
27
+ including but not limited to software source code, documentation
28
+ source, and configuration files.
29
+
30
+ "Object" form shall mean any form resulting from mechanical
31
+ transformation or translation of a Source form, including but
32
+ not limited to compiled object code, generated documentation,
33
+ and conversions to other media types.
34
+
35
+ "Work" shall mean the work of authorship, whether in Source or
36
+ Object form, made available under the License, as indicated by a
37
+ copyright notice that is included in or attached to the work
38
+ (an example is provided in the Appendix below).
39
+
40
+ "Derivative Works" shall mean any work, whether in Source or Object
41
+ form, that is based on (or derived from) the Work and for which the
42
+ editorial revisions, annotations, elaborations, or other modifications
43
+ represent, as a whole, an original work of authorship. For the purposes
44
+ of this License, Derivative Works shall not include works that remain
45
+ separable from, or merely link (or bind by name) to the interfaces of,
46
+ the Work and Derivative Works thereof.
47
+
48
+ "Contribution" shall mean any work of authorship, including
49
+ the original version of the Work and any modifications or additions
50
+ to that Work or Derivative Works thereof, that is intentionally
51
+ submitted to Licensor for inclusion in the Work by the copyright owner
52
+ or by an individual or Legal Entity authorized to submit on behalf of
53
+ the copyright owner. For the purposes of this definition, "submitted"
54
+ means any form of electronic, verbal, or written communication sent
55
+ to the Licensor or its representatives, including but not limited to
56
+ communication on electronic mailing lists, source code control systems,
57
+ and issue tracking systems that are managed by, or on behalf of, the
58
+ Licensor for the purpose of discussing and improving the Work, but
59
+ excluding communication that is conspicuously marked or otherwise
60
+ designated in writing by the copyright owner as "Not a Contribution."
61
+
62
+ "Contributor" shall mean Licensor and any individual or Legal Entity
63
+ on behalf of whom a Contribution has been received by Licensor and
64
+ subsequently incorporated within the Work.
65
+
66
+ 2. Grant of Copyright License. Subject to the terms and conditions of
67
+ this License, each Contributor hereby grants to You a perpetual,
68
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69
+ copyright license to reproduce, prepare Derivative Works of,
70
+ publicly display, publicly perform, sublicense, and distribute the
71
+ Work and such Derivative Works in Source or Object form.
72
+
73
+ 3. Grant of Patent License. Subject to the terms and conditions of
74
+ this License, each Contributor hereby grants to You a perpetual,
75
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76
+ (except as stated in this section) patent license to make, have made,
77
+ use, offer to sell, sell, import, and otherwise transfer the Work,
78
+ where such license applies only to those patent claims licensable
79
+ by such Contributor that are necessarily infringed by their
80
+ Contribution(s) alone or by combination of their Contribution(s)
81
+ with the Work to which such Contribution(s) was submitted. If You
82
+ institute patent litigation against any entity (including a
83
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
84
+ or a Contribution incorporated within the Work constitutes direct
85
+ or contributory patent infringement, then any patent licenses
86
+ granted to You under this License for that Work shall terminate
87
+ as of the date such litigation is filed.
88
+
89
+ 4. Redistribution. You may reproduce and distribute copies of the
90
+ Work or Derivative Works thereof in any medium, with or without
91
+ modifications, and in Source or Object form, provided that You
92
+ meet the following conditions:
93
+
94
+ (a) You must give any other recipients of the Work or
95
+ Derivative Works a copy of this License; and
96
+
97
+ (b) You must cause any modified files to carry prominent notices
98
+ stating that You changed the files; and
99
+
100
+ (c) You must retain, in the Source form of any Derivative Works
101
+ that You distribute, all copyright, patent, trademark, and
102
+ attribution notices from the Source form of the Work,
103
+ excluding those notices that do not pertain to any part of
104
+ the Derivative Works; and
105
+
106
+ (d) If the Work includes a "NOTICE" text file as part of its
107
+ distribution, then any Derivative Works that You distribute must
108
+ include a readable copy of the attribution notices contained
109
+ within such NOTICE file, excluding those notices that do not
110
+ pertain to any part of the Derivative Works, in at least one
111
+ of the following places: within a NOTICE text file distributed
112
+ as part of the Derivative Works; within the Source form or
113
+ documentation, if provided along with the Derivative Works; or,
114
+ within a display generated by the Derivative Works, if and
115
+ wherever such third-party notices normally appear. The contents
116
+ of the NOTICE file are for informational purposes only and
117
+ do not modify the License. You may add Your own attribution
118
+ notices within Derivative Works that You distribute, alongside
119
+ or as an addendum to the NOTICE text from the Work, provided
120
+ that such additional attribution notices cannot be construed
121
+ as modifying the License.
122
+
123
+ You may add Your own copyright statement to Your modifications and
124
+ may provide additional or different license terms and conditions
125
+ for use, reproduction, or distribution of Your modifications, or
126
+ for any such Derivative Works as a whole, provided Your use,
127
+ reproduction, and distribution of the Work otherwise complies with
128
+ the conditions stated in this License.
129
+
130
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
131
+ any Contribution intentionally submitted for inclusion in the Work
132
+ by You to the Licensor shall be under the terms and conditions of
133
+ this License, without any additional terms or conditions.
134
+ Notwithstanding the above, nothing herein shall supersede or modify
135
+ the terms of any separate license agreement you may have executed
136
+ with Licensor regarding such Contributions.
137
+
138
+ 6. Trademarks. This License does not grant permission to use the trade
139
+ names, trademarks, service marks, or product names of the Licensor,
140
+ except as required for reasonable and customary use in describing the
141
+ origin of the Work and reproducing the content of the NOTICE file.
142
+
143
+ 7. Disclaimer of Warranty. Unless required by applicable law or
144
+ agreed to in writing, Licensor provides the Work (and each
145
+ Contributor provides its Contributions) on an "AS IS" BASIS,
146
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147
+ implied, including, without limitation, any warranties or conditions
148
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149
+ PARTICULAR PURPOSE. You are solely responsible for determining the
150
+ appropriateness of using or redistributing the Work and assume any
151
+ risks associated with Your exercise of permissions under this License.
152
+
153
+ 8. Limitation of Liability. In no event and under no legal theory,
154
+ whether in tort (including negligence), contract, or otherwise,
155
+ unless required by applicable law (such as deliberate and grossly
156
+ negligent acts) or agreed to in writing, shall any Contributor be
157
+ liable to You for damages, including any direct, indirect, special,
158
+ incidental, or consequential damages of any character arising as a
159
+ result of this License or out of the use or inability to use the
160
+ Work (including but not limited to damages for loss of goodwill,
161
+ work stoppage, computer failure or malfunction, or any and all
162
+ other commercial damages or losses), even if such Contributor
163
+ has been advised of the possibility of such damages.
164
+
165
+ 9. Accepting Warranty or Additional Liability. While redistributing
166
+ the Work or Derivative Works thereof, You may choose to offer,
167
+ and charge a fee for, acceptance of support, warranty, indemnity,
168
+ or other liability obligations and/or rights consistent with this
169
+ License. However, in accepting such obligations, You may act only
170
+ on Your own behalf and on Your sole responsibility, not on behalf
171
+ of any other Contributor, and only if You agree to indemnify,
172
+ defend, and hold each Contributor harmless for any liability
173
+ incurred by, or claims asserted against, such Contributor by reason
174
+ of your accepting any such warranty or additional liability.
175
+
176
+ END OF TERMS AND CONDITIONS
177
+
178
+ APPENDIX: How to apply the Apache License to your work.
179
+
180
+ To apply the Apache License to your work, attach the following
181
+ boilerplate notice, with the fields enclosed by brackets "[]"
182
+ replaced with your own identifying information. (Don't include
183
+ the brackets!) The text should be enclosed in the appropriate
184
+ comment syntax for the file format. We also recommend that a
185
+ file or class name and description of purpose be included on the
186
+ same "printed page" as the copyright notice for easier
187
+ identification within third-party archives.
188
+
189
+ Copyright [yyyy] [name of copyright owner]
190
+
191
+ Licensed under the Apache License, Version 2.0 (the "License");
192
+ you may not use this file except in compliance with the License.
193
+ You may obtain a copy of the License at
194
+
195
+ http://www.apache.org/licenses/LICENSE-2.0
196
+
197
+ Unless required by applicable law or agreed to in writing, software
198
+ distributed under the License is distributed on an "AS IS" BASIS,
199
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200
+ See the License for the specific language governing permissions and
201
+ limitations under the License.
@@ -0,0 +1,119 @@
1
+ # Maintenance runbook
2
+
3
+ Expected effort: **2 minutes a week, 15 minutes a month, about 2 hours each May.**
4
+ The forecast archive runs and heals itself. Your job is to notice when it can't.
5
+
6
+ All commands run from the repo folder with the virtual environment active:
7
+
8
+ ```bash
9
+ cd ~/Desktop/"Tirth Private"/ew-energy-models
10
+ source .venv/bin/activate
11
+ git pull # always first: the archive bot commits to main twice a day
12
+ ```
13
+
14
+ ---
15
+
16
+ ## One-time setup
17
+
18
+ 1. **Push the repo** to `github.com/enerwyse/ew-energy-models` (public).
19
+ 2. **Bot commit permission: nothing to change.** Keep the repo/org default at
20
+ *read-only* (safer). The archive workflow requests `contents: write` in its own
21
+ file, which is all it needs. Only if its **Commit** step fails with a 403: go to
22
+ **github.com/enerwyse → Settings → Actions → General → Workflow permissions →
23
+ "Read and write permissions" → Save** (org level; the repo option is greyed out
24
+ because the org setting caps it).
25
+ 3. **Turn on failure emails:** GitHub (your avatar) → **Settings → Notifications →
26
+ Actions → "Send notifications for failed workflows only"**, with email enabled.
27
+ 4. **First archive run:** repo → **Actions → archive-ieso-forecast → Run workflow**.
28
+ It should finish green and commit `data/ieso_forecast_archive.csv`.
29
+ 5. **Reserve the PyPI name** (5 min): create an account at pypi.org, then
30
+ `pip install build twine && python -m build && twine upload dist/*`.
31
+ 6. Add a calendar reminder: **first Monday of each month, "ew-models check"**, and
32
+ **May 15, "ICI season update"**.
33
+
34
+ ## Weekly (2 minutes)
35
+
36
+ - Glance at your email for an `archive-ieso-forecast` failure. **No email = healthy.**
37
+ - Optional: repo → Actions tab. Green ticks on recent runs = fine.
38
+
39
+ ## When a failure email arrives
40
+
41
+ The failure tells you which step broke. Open the run in the **Actions** tab.
42
+
43
+ | Failed step | Meaning | What to do |
44
+ |---|---|---|
45
+ | **Gap check** | Recent dates are missing a day-ahead forecast | Click **Re-run all jobs**. Missing dates are recoverable for about 40 days, so you have time, but don't leave it. |
46
+ | **Back-fill** with an HTTP / timeout error | IESO site was down | Ignore it once. The next run heals it. If it fails for 3+ days, investigate. |
47
+ | **Back-fill** with `no ForecastOntDemand/AverageDemand series found` or a parse error | **IESO changed its report format** | See "IESO changed a format" below. |
48
+ | **Commit** with a permission error (403) | Org blocks workflow writes | Org-level fix in one-time setup step 2. |
49
+
50
+ If anything fails for more than a week, open a Claude session in the Enerwyse
51
+ project, paste the error, and ask for a fix.
52
+
53
+ ## Monthly (15 minutes, first Monday)
54
+
55
+ 1. `git pull`
56
+ 2. Check archive health:
57
+ ```bash
58
+ python scripts/archive_ieso_forecast.py --check-only
59
+ ```
60
+ You want `OK: ...`. "Known permanent gaps" are fine; IESO had already deleted those.
61
+ 3. Run the tests: `pytest -q`. All should pass. Warnings about deprecations are
62
+ a to-do, not an emergency.
63
+ 4. Check GitHub **Issues**. Reply to anything new within a week, even just
64
+ "thanks, looking at it."
65
+ 5. GitHub pauses scheduled workflows after 60 days with no repo activity. The bot's
66
+ commits normally prevent this. If the Actions tab shows a yellow "disabled"
67
+ banner, click **Enable workflow**.
68
+
69
+ ## Yearly: ICI season update (May 15–31)
70
+
71
+ The base period closes April 30. IESO publishes the final ICI demand file in early May.
72
+
73
+ 1. `git pull`
74
+ 2. Run the full analysis:
75
+ ```bash
76
+ python examples/ontario_peak_atlas.py | tee out/run_output.txt
77
+ ```
78
+ 3. Check section 1: **Matching peak hours should be 25/25** (or 5 more each year).
79
+ If any mismatch appears, stop and investigate before publishing anything.
80
+ 4. Note the new season's peak date, threshold, crowding and window coverage.
81
+ 5. Once a full summer of the forecast archive exists (first time: May 2027), run the
82
+ benchmark on real IESO forecasts (ask Claude to wire it in).
83
+ 6. Release a version (below) and publish the "ICI season in review" post.
84
+ 7. Re-read IESO's ICI page and the Class A eligibility rules for changes
85
+ (thresholds, PPA offsets, base-period dates). Update `ontario/ici.py` caveats if needed.
86
+
87
+ ## Releasing a new version
88
+
89
+ 1. Update the version in **three places**: `pyproject.toml`, `src/ewmodels/__init__.py`,
90
+ `CITATION.cff` (plus `date-released`).
91
+ 2. `pytest -q` must pass.
92
+ 3. Commit, then tag:
93
+ ```bash
94
+ git commit -am "vX.Y.Z: <what changed>"
95
+ git tag vX.Y.Z && git push && git push --tags
96
+ ```
97
+ 4. Publish: `rm -rf dist && python -m build && twine upload dist/*`
98
+ 5. GitHub → **Releases → Draft a new release** from the tag, with 3–5 lines on what's new.
99
+
100
+ ## IESO changed a format
101
+
102
+ Signs: parse errors, missing columns, or the workflow failing every run.
103
+
104
+ 1. Look at a current file in the browser:
105
+ `https://reports-public.ieso.ca/public/Adequacy3/` or `.../Demand/`
106
+ 2. For forecasts, run `python -m ewmodels.data.ieso_forecast --inspect` and see
107
+ which series it finds.
108
+ 3. Ask Claude with the error and the inspect output. Fix, add a test, release.
109
+ 4. **Time matters for the forecast archive:** versions disappear after about 45 days.
110
+
111
+ ## Rules that never change
112
+
113
+ - **No customer data in this repo, ever.** That covers meter data, Green Button
114
+ files, and anything from the Enerwyse platform, anonymized or not. `.gitignore`
115
+ blocks XML, but the rule is yours to keep.
116
+ - Every new model returns a `Result` with a receipt: checks plus caveats.
117
+ - Every change comes with a test. Tests must pass before pushing.
118
+ - Charts use fixed axes (never let autoscale exaggerate a small difference).
119
+ - Anything published publicly gets verified against an official source first.
@@ -0,0 +1,8 @@
1
+ ew-energy-models
2
+ Copyright 2026 Enerwyse
3
+
4
+ This product includes software developed by Enerwyse (https://enerwyse.ca).
5
+
6
+ Data accessed by this software at runtime from the Independent Electricity
7
+ System Operator (IESO) public reports site is the property of the IESO and is
8
+ not redistributed with this package.
@@ -0,0 +1,155 @@
1
+ Metadata-Version: 2.5
2
+ Name: ew-energy-models
3
+ Version: 0.2.0
4
+ Summary: Open, auditable energy-analytics models from Enerwyse: load analytics, M&V baselines, and Ontario Global Adjustment / ICI peak tools. Every result ships with an evidence receipt.
5
+ Project-URL: Homepage, https://enerwyse.ca
6
+ Project-URL: Source, https://github.com/enerwyse/ew-energy-models
7
+ Author-email: Enerwyse <tirth.shah@enerwyse.ca>
8
+ License-Expression: Apache-2.0
9
+ License-File: LICENSE
10
+ License-File: NOTICE
11
+ Keywords: ashrae-guideline-14,emis,energy,global-adjustment,ici,ieso,measurement-and-verification,ontario
12
+ Classifier: Development Status :: 3 - Alpha
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: Intended Audience :: Science/Research
15
+ Classifier: License :: OSI Approved :: Apache Software License
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Topic :: Scientific/Engineering
18
+ Requires-Python: >=3.10
19
+ Requires-Dist: numpy>=1.24
20
+ Requires-Dist: pandas>=2.0
21
+ Provides-Extra: dev
22
+ Requires-Dist: pytest>=8; extra == 'dev'
23
+ Provides-Extra: plot
24
+ Requires-Dist: matplotlib>=3.7; extra == 'plot'
25
+ Description-Content-Type: text/markdown
26
+
27
+ # ew-energy-models
28
+
29
+ **Open, auditable energy-analytics models from [Enerwyse](https://enerwyse.ca).**
30
+
31
+ `ewmodels` is a small Python library of energy models for commercial and
32
+ industrial facilities. It covers load analytics, M&V baselines, and Ontario
33
+ Global Adjustment / Industrial Conservation Initiative (ICI) peak tools.
34
+
35
+ Every model returns a result **and an evidence receipt**. The receipt records:
36
+
37
+ - the input data hash
38
+ - the detected interval resolution
39
+ - data-sufficiency checks, each pass/fail
40
+ - the simplifying assumptions the result relies on
41
+ - the code version that produced it
42
+
43
+ A number without its receipt is not a result.
44
+
45
+ ```python
46
+ from ewmodels.data.ieso import fetch_demand
47
+ from ewmodels.ontario import atlas, ici
48
+
49
+ demand = fetch_demand(range(2022, 2027))["ontario_demand_mw"]
50
+ res = atlas.peak_atlas(demand)
51
+
52
+ print(res.value["summary"]) # per base period: 5th-peak threshold, crowding
53
+ print(res.receipt.summary()) # what this number rests on
54
+ ```
55
+
56
+ ## Why this exists
57
+
58
+ Energy analytics usually comes as a black box: a vendor's number on a
59
+ dashboard. Facility managers, M&V reviewers and incentive programs then have to
60
+ take it on trust. We think the math should be open and every figure should be
61
+ checkable. The value a platform adds is applying the math reliably to real
62
+ operations, not hiding it.
63
+
64
+ ## What's in v0.2: Ontario ICI toolkit
65
+
66
+ | Module | What it does |
67
+ |---|---|
68
+ | `ewmodels.data.ieso` | Downloads and parses IESO hourly Ontario demand and the **ICI Demand** reports. Converts hour-ending EST to interval-start timestamps. Caches locally. |
69
+ | `ewmodels.data.ieso_forecast` | Parses IESO's Ontario demand forecasts (Adequacy3) and keeps an **open forecast archive** |
70
+ | `ewmodels.ontario.ici` | Base periods, top-5 peak hours, **Peak Demand Factor** from interval meter data, GA charge, an optional **clean-energy PPA offset**, and curtailment-savings simulation |
71
+ | `ewmodels.ontario.atlas` | **Ontario Peak Atlas**: per-season thresholds, peak timing, and *crowding*, i.e. how many days came within 1–5% of the 5th peak |
72
+ | `ewmodels.ontario.benchmark` | **Open 5CP benchmark**: replays history day by day with no look-ahead and scores any peak-calling strategy on *peaks captured vs. days curtailed*. Supports synthetic or real forecasts, hour-level scoring with a curtailment window, window-length analysis, and a **growth-removed stress test** |
73
+
74
+ ### The open 5CP benchmark
75
+
76
+ Accuracy scores don't capture what a Class A customer actually trades off. A
77
+ strategy that calls every hot weekday catches every peak and disrupts
78
+ operations all summer. One that calls three days may miss the peak that
79
+ mattered. The benchmark gives a strategy only what was knowable before each
80
+ day, then scores it in operational terms:
81
+
82
+ ```python
83
+ from ewmodels.ontario import benchmark
84
+
85
+ result = benchmark.evaluate(demand, benchmark.threshold_strategy(margin=0.03),
86
+ forecast_error_pct=2.0)
87
+ print(result.value) # per base period: days_called, peak_hours_captured, capture_rate
88
+ ```
89
+
90
+ Any function `DayContext -> bool` is a strategy, so you can plug in your own
91
+ forecaster and compare it on the same terms.
92
+
93
+ **Two honesty guards are built in:**
94
+
95
+ - **Stress test.** Ontario's peak has grown every recent season, which flatters any
96
+ rule anchored on last year's peaks. `normalize_seasons()` removes that growth, so
97
+ a strategy has to work in a flat year too.
98
+ - **Real windows.** All five peak hours usually fall on one afternoon, as
99
+ consecutive hours. `window=(start, hours)` scores what a fixed curtailment window
100
+ actually catches, and `window_coverage()` shows how long the window has to be.
101
+
102
+ ### The open IESO forecast archive
103
+
104
+ IESO keeps its Ontario demand forecasts for only about 90 days, so no public
105
+ record exists of what was forecast against what happened. This repo builds one:
106
+ a scheduled workflow snapshots IESO's forecasts twice a day into
107
+ `data/ieso_forecast_archive.csv`. Once a full ICI season is archived, the
108
+ benchmark can score strategies on the forecasts operators actually saw.
109
+
110
+ ## Design principles
111
+
112
+ 1. **Receipts, not just results.** Checks fail loudly: for example, a missing meter
113
+ reading during a peak hour makes the PDF receipt invalid, rather than
114
+ silently computing a wrong PDF.
115
+ 2. **Resolution is data, not configuration.** Utility interval data arrives at
116
+ 5, 15 or 60 minutes. Models detect the resolution and never assume it.
117
+ 3. **Deterministic and reproducible.** The same inputs always give the same
118
+ outputs, and randomness is seeded.
119
+ 4. **No customer data, ever.** The library ships no meter data. Examples use
120
+ public sources only (IESO, Environment Canada, open building datasets).
121
+
122
+ ## Roadmap
123
+
124
+ The library will grow to cover the Enerwyse models catalogue:
125
+
126
+ - **v0.3 Load kernels:** load-profile decomposition, peak ranking, load factor
127
+ - **v0.4 M&V:** ASHRAE Guideline 14 change-point baselines (3P/4P/5P), weather
128
+ normalization, CUSUM tracking, with CV(RMSE)/NMBE validity in the receipt
129
+ - **Later:** day-ahead load forecasting, load-shifting optimization, and
130
+ carbon-intensity scheduling
131
+
132
+ ## Install
133
+
134
+ ```bash
135
+ pip install ew-energy-models # once published
136
+ # or from source
137
+ pip install -e ".[dev]" && pytest
138
+ ```
139
+
140
+ ## Important
141
+
142
+ This is an analysis tool, not a settlement engine. Peak hours reproduced from
143
+ public demand data are provisional until the IESO publishes the official ones.
144
+ PDFs computed with Ontario Demand as the denominator are estimates. Your LDC or
145
+ IESO invoice is authoritative. Nothing here is financial or regulatory advice.
146
+
147
+ ## Maintenance
148
+
149
+ See [MAINTENANCE.md](MAINTENANCE.md) for the runbook: weekly checks, alerts, yearly updates, releases.
150
+
151
+ ## License & citation
152
+
153
+ Apache-2.0. If you use this in research, please cite it (see `CITATION.cff`).
154
+ IESO data © Independent Electricity System Operator. It is fetched at runtime,
155
+ not redistributed.