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.
- ew_energy_models-0.2.0/.github/workflows/archive-forecast.yml +38 -0
- ew_energy_models-0.2.0/.github/workflows/publish.yml +51 -0
- ew_energy_models-0.2.0/.github/workflows/tests.yml +23 -0
- ew_energy_models-0.2.0/.gitignore +13 -0
- ew_energy_models-0.2.0/CITATION.cff +17 -0
- ew_energy_models-0.2.0/LICENSE +201 -0
- ew_energy_models-0.2.0/MAINTENANCE.md +119 -0
- ew_energy_models-0.2.0/NOTICE +8 -0
- ew_energy_models-0.2.0/PKG-INFO +155 -0
- ew_energy_models-0.2.0/README.md +129 -0
- ew_energy_models-0.2.0/data/.gitkeep +0 -0
- ew_energy_models-0.2.0/data/ieso_forecast_archive.csv +5281 -0
- ew_energy_models-0.2.0/examples/ontario_peak_atlas.py +105 -0
- ew_energy_models-0.2.0/pyproject.toml +37 -0
- ew_energy_models-0.2.0/scripts/archive_ieso_forecast.py +54 -0
- ew_energy_models-0.2.0/src/ewmodels/__init__.py +11 -0
- ew_energy_models-0.2.0/src/ewmodels/data/__init__.py +1 -0
- ew_energy_models-0.2.0/src/ewmodels/data/ieso.py +103 -0
- ew_energy_models-0.2.0/src/ewmodels/data/ieso_backfill.py +133 -0
- ew_energy_models-0.2.0/src/ewmodels/data/ieso_forecast.py +179 -0
- ew_energy_models-0.2.0/src/ewmodels/ontario/__init__.py +2 -0
- ew_energy_models-0.2.0/src/ewmodels/ontario/atlas.py +67 -0
- ew_energy_models-0.2.0/src/ewmodels/ontario/benchmark.py +252 -0
- ew_energy_models-0.2.0/src/ewmodels/ontario/ici.py +192 -0
- ew_energy_models-0.2.0/src/ewmodels/receipts.py +104 -0
- ew_energy_models-0.2.0/src/ewmodels/resolution.py +40 -0
- ew_energy_models-0.2.0/tests/__init__.py +0 -0
- ew_energy_models-0.2.0/tests/conftest.py +33 -0
- ew_energy_models-0.2.0/tests/fixtures/PUB_Demand_sample.csv +8 -0
- ew_energy_models-0.2.0/tests/test_backfill.py +61 -0
- ew_energy_models-0.2.0/tests/test_core.py +44 -0
- ew_energy_models-0.2.0/tests/test_forecast.py +66 -0
- 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,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.
|