tariffkit 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.
- tariffkit-0.2.0/.gitignore +33 -0
- tariffkit-0.2.0/CHANGELOG.md +172 -0
- tariffkit-0.2.0/LICENSE +21 -0
- tariffkit-0.2.0/PKG-INFO +260 -0
- tariffkit-0.2.0/README.md +215 -0
- tariffkit-0.2.0/pyproject.toml +152 -0
- tariffkit-0.2.0/src/tariffkit/__init__.py +52 -0
- tariffkit-0.2.0/src/tariffkit/account/__init__.py +41 -0
- tariffkit-0.2.0/src/tariffkit/account/cli.py +493 -0
- tariffkit-0.2.0/src/tariffkit/account/errors.py +25 -0
- tariffkit-0.2.0/src/tariffkit/account/model.py +641 -0
- tariffkit-0.2.0/src/tariffkit/account/rates.py +61 -0
- tariffkit-0.2.0/src/tariffkit/account/repository.py +329 -0
- tariffkit-0.2.0/src/tariffkit/billing/__init__.py +56 -0
- tariffkit-0.2.0/src/tariffkit/billing/engine.py +505 -0
- tariffkit-0.2.0/src/tariffkit/billing/ledger.py +365 -0
- tariffkit-0.2.0/src/tariffkit/billing/models.py +274 -0
- tariffkit-0.2.0/src/tariffkit/billing/netting.py +137 -0
- tariffkit-0.2.0/src/tariffkit/billing/trueup.py +498 -0
- tariffkit-0.2.0/src/tariffkit/cca.py +122 -0
- tariffkit-0.2.0/src/tariffkit/cli.py +809 -0
- tariffkit-0.2.0/src/tariffkit/config.py +296 -0
- tariffkit-0.2.0/src/tariffkit/data/__init__.py +40 -0
- tariffkit-0.2.0/src/tariffkit/data/cca/mce/2023-01-01.toml +61 -0
- tariffkit-0.2.0/src/tariffkit/data/cca/mce/2026-04-01.toml +152 -0
- tariffkit-0.2.0/src/tariffkit/data/export/pge/acc_plus/2023-04-15.toml +43 -0
- tariffkit-0.2.0/src/tariffkit/data/export/pge/nbt00.json.gz +0 -0
- tariffkit-0.2.0/src/tariffkit/data/export/pge/nbt23.json.gz +0 -0
- tariffkit-0.2.0/src/tariffkit/data/export/pge/nbt24.json.gz +0 -0
- tariffkit-0.2.0/src/tariffkit/data/export/pge/nbt25.json.gz +0 -0
- tariffkit-0.2.0/src/tariffkit/data/export/pge/nbt26.json.gz +0 -0
- tariffkit-0.2.0/src/tariffkit/data/holidays.toml +36 -0
- tariffkit-0.2.0/src/tariffkit/data/manifest.json +56 -0
- tariffkit-0.2.0/src/tariffkit/data/nsc/pge.toml +57 -0
- tariffkit-0.2.0/src/tariffkit/data/tariff/pge/eelec/2025-01-01.toml +151 -0
- tariffkit-0.2.0/src/tariffkit/data/tariff/pge/eelec/2025-03-01.toml +150 -0
- tariffkit-0.2.0/src/tariffkit/data/tariff/pge/eelec/2025-09-01.toml +150 -0
- tariffkit-0.2.0/src/tariffkit/data/tariff/pge/eelec/2026-01-01.toml +153 -0
- tariffkit-0.2.0/src/tariffkit/data/tariff/pge/eelec/2026-03-01.toml +157 -0
- tariffkit-0.2.0/src/tariffkit/data/tariff/pge/etouc/2025-01-01.toml +222 -0
- tariffkit-0.2.0/src/tariffkit/data/tariff/pge/etouc/2025-03-01.toml +221 -0
- tariffkit-0.2.0/src/tariffkit/data/tariff/pge/etouc/2025-09-01.toml +221 -0
- tariffkit-0.2.0/src/tariffkit/data/tariff/pge/etouc/2026-01-01.toml +224 -0
- tariffkit-0.2.0/src/tariffkit/data/tariff/pge/etouc/2026-03-01.toml +231 -0
- tariffkit-0.2.0/src/tariffkit/data/tariff/pge/ev2a/2025-01-01.toml +144 -0
- tariffkit-0.2.0/src/tariffkit/data/tariff/pge/ev2a/2025-03-01.toml +143 -0
- tariffkit-0.2.0/src/tariffkit/data/tariff/pge/ev2a/2025-09-01.toml +143 -0
- tariffkit-0.2.0/src/tariffkit/data/tariff/pge/ev2a/2026-01-01.toml +146 -0
- tariffkit-0.2.0/src/tariffkit/data/tariff/pge/ev2a/2026-03-01.toml +153 -0
- tariffkit-0.2.0/src/tariffkit/data/tax/ca_energy_resources/2025-01-01.toml +27 -0
- tariffkit-0.2.0/src/tariffkit/data/tax/ca_energy_resources/2026-01-01.toml +27 -0
- tariffkit-0.2.0/src/tariffkit/data/versioned.py +118 -0
- tariffkit-0.2.0/src/tariffkit/engine.py +82 -0
- tariffkit-0.2.0/src/tariffkit/errors.py +19 -0
- tariffkit-0.2.0/src/tariffkit/export/__init__.py +5 -0
- tariffkit-0.2.0/src/tariffkit/export/nbt.py +207 -0
- tariffkit-0.2.0/src/tariffkit/interop/__init__.py +21 -0
- tariffkit-0.2.0/src/tariffkit/interop/emhass.py +79 -0
- tariffkit-0.2.0/src/tariffkit/interop/predbat.py +102 -0
- tariffkit-0.2.0/src/tariffkit/interop/slots.py +63 -0
- tariffkit-0.2.0/src/tariffkit/models.py +164 -0
- tariffkit-0.2.0/src/tariffkit/mqtt/__init__.py +6 -0
- tariffkit-0.2.0/src/tariffkit/mqtt/discovery.py +84 -0
- tariffkit-0.2.0/src/tariffkit/mqtt/publisher.py +305 -0
- tariffkit-0.2.0/src/tariffkit/providers/__init__.py +1 -0
- tariffkit-0.2.0/src/tariffkit/providers/pge/__init__.py +33 -0
- tariffkit-0.2.0/src/tariffkit/providers/pge/reconcile.py +828 -0
- tariffkit-0.2.0/src/tariffkit/providers/pge/statements/__init__.py +26 -0
- tariffkit-0.2.0/src/tariffkit/providers/pge/statements/errors.py +20 -0
- tariffkit-0.2.0/src/tariffkit/providers/pge/statements/model.py +320 -0
- tariffkit-0.2.0/src/tariffkit/providers/pge/statements/ocr.py +193 -0
- tariffkit-0.2.0/src/tariffkit/providers/pge/statements/parse.py +813 -0
- tariffkit-0.2.0/src/tariffkit/py.typed +0 -0
- tariffkit-0.2.0/src/tariffkit/secrets.py +164 -0
- tariffkit-0.2.0/src/tariffkit/sources/__init__.py +71 -0
- tariffkit-0.2.0/src/tariffkit/sources/greenbutton.py +318 -0
- tariffkit-0.2.0/src/tariffkit/sources/homeassistant.py +342 -0
- tariffkit-0.2.0/src/tariffkit/sources/influx.py +359 -0
- tariffkit-0.2.0/src/tariffkit/sources/pge.py +1153 -0
- tariffkit-0.2.0/src/tariffkit/tariff/__init__.py +5 -0
- tariffkit-0.2.0/src/tariffkit/tariff/retail.py +271 -0
- tariffkit-0.2.0/src/tariffkit/timeutil.py +120 -0
- tariffkit-0.2.0/src/tariffkit/web/__init__.py +5 -0
- tariffkit-0.2.0/src/tariffkit/web/app.py +220 -0
- tariffkit-0.2.0/tests/conftest.py +26 -0
- tariffkit-0.2.0/tests/fixtures/nbt_golden_rows.jsonl +2120 -0
- tariffkit-0.2.0/tests/fixtures/statements/synthetic_cca_ratechange.txt +77 -0
- tariffkit-0.2.0/tests/test_account.py +354 -0
- tariffkit-0.2.0/tests/test_account_cli.py +422 -0
- tariffkit-0.2.0/tests/test_billing.py +533 -0
- tariffkit-0.2.0/tests/test_eelec.py +274 -0
- tariffkit-0.2.0/tests/test_engine.py +330 -0
- tariffkit-0.2.0/tests/test_ev2a.py +177 -0
- tariffkit-0.2.0/tests/test_export_golden.py +101 -0
- tariffkit-0.2.0/tests/test_integrations.py +209 -0
- tariffkit-0.2.0/tests/test_interop.py +322 -0
- tariffkit-0.2.0/tests/test_ledger.py +270 -0
- tariffkit-0.2.0/tests/test_mce.py +225 -0
- tariffkit-0.2.0/tests/test_mqtt_publisher.py +243 -0
- tariffkit-0.2.0/tests/test_pge_reconcile.py +387 -0
- tariffkit-0.2.0/tests/test_pge_statements.py +128 -0
- tariffkit-0.2.0/tests/test_private_config.py +193 -0
- tariffkit-0.2.0/tests/test_schedules.py +256 -0
- tariffkit-0.2.0/tests/test_sources_greenbutton.py +212 -0
- tariffkit-0.2.0/tests/test_sources_ha.py +372 -0
- tariffkit-0.2.0/tests/test_sources_influx.py +425 -0
- tariffkit-0.2.0/tests/test_sources_pge.py +300 -0
- tariffkit-0.2.0/tests/test_trueup.py +293 -0
- tariffkit-0.2.0/tests/test_versioned.py +137 -0
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
__pycache__/
|
|
2
|
+
*.py[cod]
|
|
3
|
+
*.egg-info/
|
|
4
|
+
.eggs/
|
|
5
|
+
build/
|
|
6
|
+
dist/
|
|
7
|
+
.venv/
|
|
8
|
+
venv/
|
|
9
|
+
.env
|
|
10
|
+
.env.bak*
|
|
11
|
+
|
|
12
|
+
# One real account's tariff history. audit/account.example.toml is the template.
|
|
13
|
+
audit/account.toml
|
|
14
|
+
|
|
15
|
+
# Statements carry a name, an address, an account number and a year of usage.
|
|
16
|
+
# They are read wherever they already sit and never committed; the audit tests
|
|
17
|
+
# use synthetic fixtures instead. Downloads land in .cache/, ignored above.
|
|
18
|
+
*.pdf
|
|
19
|
+
|
|
20
|
+
.pytest_cache/
|
|
21
|
+
.mypy_cache/
|
|
22
|
+
.ruff_cache/
|
|
23
|
+
.coverage
|
|
24
|
+
htmlcov/
|
|
25
|
+
|
|
26
|
+
# Upstream archives and documents fetched by `python -m tools.regen` — large, not
|
|
27
|
+
# vendored. Also where downloaded statements and Green Button exports land.
|
|
28
|
+
# The normalized outputs under src/tariffkit/data/ ARE committed.
|
|
29
|
+
/.cache/
|
|
30
|
+
*.zip
|
|
31
|
+
!tests/fixtures/*.zip
|
|
32
|
+
|
|
33
|
+
.DS_Store
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project are documented here. This project follows
|
|
4
|
+
[Semantic Versioning](https://semver.org/).
|
|
5
|
+
|
|
6
|
+
## [Unreleased]
|
|
7
|
+
|
|
8
|
+
## [0.2.0] - 2026-08-16
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
- **Named, effective-dated account profiles** track tariff, supplier, baseline,
|
|
12
|
+
export, and credential-set changes over a service agreement's lifetime.
|
|
13
|
+
Pricing and billing resolve the settings in force at each timestamp, including
|
|
14
|
+
cycles that cross an account transition. Profile writes are atomic,
|
|
15
|
+
revision-checked, and locked against concurrent updates.
|
|
16
|
+
- **PG&E statement import and portal synchronization** can populate an account
|
|
17
|
+
profile from printed facts. Proposed changes are reported as additions,
|
|
18
|
+
confirmations, conflicts, or missing required values; a statement with a
|
|
19
|
+
conflict cannot be partially applied. Statement support is available through
|
|
20
|
+
the `statements` extra, while the account-specific audit harness remains
|
|
21
|
+
repository-only.
|
|
22
|
+
- **Profile-scoped meter sources** store provider-neutral grid-import and
|
|
23
|
+
grid-export mappings for Home Assistant and InfluxDB 3. The bill command can
|
|
24
|
+
query either source directly, with explicit command options taking precedence
|
|
25
|
+
over profile, environment, and global defaults.
|
|
26
|
+
- **Credential sets backed by the operating-system keyring** hold PG&E, Home
|
|
27
|
+
Assistant, InfluxDB, and MQTT secrets outside configuration files and command
|
|
28
|
+
arguments. Environment injection remains available for containers.
|
|
29
|
+
- **A complete billing layer** now covers interval netting, coverage warnings,
|
|
30
|
+
baseline credits, fixed charges, taxes, export-credit buckets, annual true-up,
|
|
31
|
+
and CCA cash-out. Credits retain their printed statement scope instead of
|
|
32
|
+
being treated as one fungible balance.
|
|
33
|
+
- **Home Assistant and InfluxDB 3 interval sources** complement the Green Button
|
|
34
|
+
reader. Home Assistant reads long-term statistics and prefers five-minute
|
|
35
|
+
data; InfluxDB derives exact totals from cumulative-counter endpoints and
|
|
36
|
+
spreads advances across the time in which they accrued.
|
|
37
|
+
- **E-TOU-C and EV2-A retail schedules**, complete PCIA and franchise-fee
|
|
38
|
+
vintage tables, E-TOU-C baseline allowances, California's electrical-energy
|
|
39
|
+
surcharge, Net Surplus Compensation data, and effective-dated PG&E and MCE
|
|
40
|
+
rate snapshots extend pricing and billing beyond the original E-ELEC
|
|
41
|
+
schedule.
|
|
42
|
+
- **Repository-only rate-data generators** rebuild every vendored dataset from
|
|
43
|
+
its published source and read the rendered result back through runtime code
|
|
44
|
+
before writing it. A weekly workflow checks export matrices, retail tariffs,
|
|
45
|
+
ACC Plus, CCA cards, Net Surplus Compensation, holidays, and the state
|
|
46
|
+
surcharge for upstream changes.
|
|
47
|
+
- **Home Assistant account-history flows** support staged initial setup and
|
|
48
|
+
profile inspection, transition editing, statement import, and sanitized
|
|
49
|
+
profile export. Stable profile-based config-entry identity survives tariff and
|
|
50
|
+
supplier changes.
|
|
51
|
+
- **Home Assistant response actions** provide current or forecast rates in
|
|
52
|
+
native and EMHASS shapes for caller-selected windows. Requests reject
|
|
53
|
+
ambiguous DST-fold timestamps, misaligned windows, and horizons beyond seven
|
|
54
|
+
days rather than guessing.
|
|
55
|
+
- **Home Assistant diagnostics and integration tests** cover config and options
|
|
56
|
+
flows, migration, entities, actions, Energy dashboard compatibility,
|
|
57
|
+
effective-dated provenance, DST handling, and opt-in Predbat output.
|
|
58
|
+
Diagnostics deliberately omit account history, observations, credentials,
|
|
59
|
+
and meter mappings.
|
|
60
|
+
- **Pure interoperability adapters** generate EMHASS, Predbat, and generic slot
|
|
61
|
+
payloads from a `PriceCurve`, shared by Home Assistant and MQTT without either
|
|
62
|
+
integration becoming a dependency.
|
|
63
|
+
- **Request-scoped REST configuration and account selection** allow callers to
|
|
64
|
+
price one request against validated settings or a named profile. Unknown
|
|
65
|
+
configuration keys and credential fields are rejected.
|
|
66
|
+
- **A bind-mounted Home Assistant development stack** runs the custom component
|
|
67
|
+
and local package source in a real container without publishing a wheel.
|
|
68
|
+
- **A build-once release process and maintainer runbook** synchronize Python,
|
|
69
|
+
lockfile, Home Assistant, changelog, and documentation versions. One validated
|
|
70
|
+
wheel/sdist pair moves through optional TestPyPI staging, protected PyPI
|
|
71
|
+
approval, PEP 740 attestations, and an immutable GitHub release.
|
|
72
|
+
|
|
73
|
+
### Changed
|
|
74
|
+
- **The project is now TariffKit.** The distribution, import package, CLI,
|
|
75
|
+
configuration directory, environment prefix, MQTT namespace, repository
|
|
76
|
+
links, and Home Assistant domain use the utility-neutral `tariffkit` identity.
|
|
77
|
+
The initial data-provider scope remains PG&E and California.
|
|
78
|
+
- **Packaging remains one public distribution in one repository.** Optional
|
|
79
|
+
features use extras and lazy imports, leaving the default runtime
|
|
80
|
+
dependency-free. Rate-data tooling and the account-specific audit harness are
|
|
81
|
+
excluded from wheel and sdist but retain strict lint, typing, and test
|
|
82
|
+
coverage.
|
|
83
|
+
- **Python 3.14 is the minimum**, and Home Assistant 2026.3.0 is therefore the
|
|
84
|
+
integration floor. Development dependencies use PEP 735 groups, package
|
|
85
|
+
licensing uses an SPDX expression, and installed metadata is the runtime
|
|
86
|
+
version source.
|
|
87
|
+
- **Home Assistant installs the exact matching TariffKit release** instead of
|
|
88
|
+
vendoring package source. Its config flow asks only questions made relevant
|
|
89
|
+
by earlier answers and separates account history from forecast and Predbat
|
|
90
|
+
options.
|
|
91
|
+
- **Home Assistant entities are lean and recorder-safe.** Import and export
|
|
92
|
+
price sensors remain compatible with the Energy dashboard, a timestamp entity
|
|
93
|
+
reports forecast coverage, and large forecast or optimizer payloads are not
|
|
94
|
+
recorded every minute. The fixed daily charge is no longer represented as a
|
|
95
|
+
marginal price sensor.
|
|
96
|
+
- **EMHASS forecasts are requested through an action and Predbat output is
|
|
97
|
+
opt-in.** Neither large payload is computed and attached to every entity when
|
|
98
|
+
unused.
|
|
99
|
+
- **Pricing provenance and quality are preserved end to end.** Core, CLI, REST,
|
|
100
|
+
MQTT, and Home Assistant outputs retain `locked`, `exact`, and `complete`
|
|
101
|
+
states and describe contiguous provenance segments across effective-date
|
|
102
|
+
boundaries.
|
|
103
|
+
- **Green Button names now describe the format rather than its container.**
|
|
104
|
+
`read_green_button`, `GreenButtonLayout`, and `--source green-button` replace
|
|
105
|
+
generic CSV terminology; the legacy source spelling remains accepted.
|
|
106
|
+
- **Strict typing now covers the Home Assistant custom component and all
|
|
107
|
+
repository tools**, while Ruff enforces function annotations throughout the
|
|
108
|
+
repository.
|
|
109
|
+
|
|
110
|
+
### Fixed
|
|
111
|
+
- Production publishing no longer inherits a false implicit failure from the
|
|
112
|
+
intentionally skipped optional TestPyPI job. The explicit release dependency
|
|
113
|
+
checks now permit protected PyPI and GitHub publication only after the build
|
|
114
|
+
and draft jobs themselves succeed.
|
|
115
|
+
- Account-profile updates no longer race between revision checks and filesystem
|
|
116
|
+
mutation, statement-derived histories reject gaps and overlaps, imported Home
|
|
117
|
+
Assistant profiles cannot replace an entry's stable identity, and MQTT
|
|
118
|
+
profile selection respects environment precedence.
|
|
119
|
+
- Baseline credits use each day and vintage's own rate, pre-PTO exports earn no
|
|
120
|
+
compensation, string supplier values are normalized before branching, and
|
|
121
|
+
historical pricing never borrows data from a future vintage.
|
|
122
|
+
- CCA generation rates are selected by PG&E schedule instead of silently using
|
|
123
|
+
E-ELEC values for every plan. PCIA, franchise-fee, MCE generation, cost-relief,
|
|
124
|
+
and premium data are resolved from their actual effective vintages.
|
|
125
|
+
- Billing coverage and interval stepping use absolute time across both DST
|
|
126
|
+
transitions. Repeated autumn timestamps are disambiguated during ingest, rate
|
|
127
|
+
points remain contiguous, and MQTT no longer sleeps through the second 01:00
|
|
128
|
+
hour.
|
|
129
|
+
- Green Button ingestion handles account preambles, split date/time columns,
|
|
130
|
+
unit-suffixed headers, explicit offsets, and repeated autumn wall times.
|
|
131
|
+
Home Assistant and InfluxDB sources report implausible resets and missing
|
|
132
|
+
coverage instead of inventing plausible energy.
|
|
133
|
+
- Export-rate lookups use each vintage's own holiday calendar and surface
|
|
134
|
+
publisher drift beyond the verified exact range rather than silently claiming
|
|
135
|
+
exact future values.
|
|
136
|
+
- Generator parsing no longer drops a PCIA row joined to the next page header,
|
|
137
|
+
and all generated files are validated through independent runtime readers
|
|
138
|
+
before replacing vendored data.
|
|
139
|
+
|
|
140
|
+
## [0.1.0] - 2026-07-28
|
|
141
|
+
|
|
142
|
+
Initial release.
|
|
143
|
+
|
|
144
|
+
### Added
|
|
145
|
+
- `RateEngine` with `price_now()`, `price_at()`, and `forecast()` for PG&E
|
|
146
|
+
E-ELEC import prices and NEM 3.0 / Net Billing Tariff export credits.
|
|
147
|
+
- Vendored rate data for all five NBT vintages (NBT23/24/25/26/00), collapsed
|
|
148
|
+
from PG&E's ~40 MB-per-vintage hourly files to 268 KiB total with verified
|
|
149
|
+
lossless round-tripping.
|
|
150
|
+
- ACC Plus adder as a first-class, separately reported component.
|
|
151
|
+
- CCA / Direct Access support: bundled generation and PCIA are dropped, and
|
|
152
|
+
delivery-only prices are flagged `complete=False` rather than understated.
|
|
153
|
+
- CLI: `now`, `forecast`, `info`, `mqtt`, `serve`.
|
|
154
|
+
- MQTT publisher with Home Assistant MQTT Discovery and a last-will
|
|
155
|
+
availability topic.
|
|
156
|
+
- FastAPI service under the `web` extra.
|
|
157
|
+
- HACS-installable Home Assistant custom component.
|
|
158
|
+
- `tools/regen_data.py` for refreshing vendored data, plus a weekly CI job that
|
|
159
|
+
fails when upstream rates change.
|
|
160
|
+
|
|
161
|
+
### Notes on upstream data
|
|
162
|
+
- PG&E's export files label the repeated 01:00 on the autumn DST transition as
|
|
163
|
+
`HS2`, so that hour is priced as 2am. Handled in `timeutil.export_hour`.
|
|
164
|
+
- From 2036 onward PG&E's own hour labels stop tracking Pacific daylight time
|
|
165
|
+
and NBT25/26/00 duplicate some holidays onto the following day. The verified
|
|
166
|
+
boundary is recorded per vintage as `exact_through` and surfaced as
|
|
167
|
+
`ExportPrice.exact`. Every year within a nine-year rate lock is exact.
|
|
168
|
+
- Holiday calendars are extracted per vintage from the source data rather than
|
|
169
|
+
recomputed, because the vintage files disagree in far-future years.
|
|
170
|
+
|
|
171
|
+
[Unreleased]: https://github.com/eman/tariffkit/compare/v0.2.0...HEAD
|
|
172
|
+
[0.2.0]: https://github.com/eman/tariffkit/releases/tag/v0.2.0
|
tariffkit-0.2.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Emmanuel
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
tariffkit-0.2.0/PKG-INFO
ADDED
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: tariffkit
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: Offline electricity tariff pricing, billing, and integration tools
|
|
5
|
+
Project-URL: Homepage, https://github.com/eman/tariffkit
|
|
6
|
+
Project-URL: Documentation, https://github.com/eman/tariffkit#documentation
|
|
7
|
+
Project-URL: Issues, https://github.com/eman/tariffkit/issues
|
|
8
|
+
Project-URL: Source, https://github.com/eman/tariffkit
|
|
9
|
+
License-Expression: MIT
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Keywords: battery,energy,home-assistant,nem3,net-billing-tariff,pge,solar,tariff
|
|
12
|
+
Classifier: Development Status :: 3 - Alpha
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: Intended Audience :: End Users/Desktop
|
|
15
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
18
|
+
Classifier: Topic :: Home Automation
|
|
19
|
+
Classifier: Typing :: Typed
|
|
20
|
+
Requires-Python: >=3.14
|
|
21
|
+
Provides-Extra: all
|
|
22
|
+
Requires-Dist: fastapi>=0.110; extra == 'all'
|
|
23
|
+
Requires-Dist: httpx>=0.27; extra == 'all'
|
|
24
|
+
Requires-Dist: keyring>=25; extra == 'all'
|
|
25
|
+
Requires-Dist: paho-mqtt>=2.0; extra == 'all'
|
|
26
|
+
Requires-Dist: pypdf>=4.0; extra == 'all'
|
|
27
|
+
Requires-Dist: uvicorn[standard]>=0.29; extra == 'all'
|
|
28
|
+
Requires-Dist: websockets>=12; extra == 'all'
|
|
29
|
+
Provides-Extra: ha
|
|
30
|
+
Requires-Dist: websockets>=12; extra == 'ha'
|
|
31
|
+
Provides-Extra: influx
|
|
32
|
+
Requires-Dist: httpx>=0.27; extra == 'influx'
|
|
33
|
+
Provides-Extra: mqtt
|
|
34
|
+
Requires-Dist: paho-mqtt>=2.0; extra == 'mqtt'
|
|
35
|
+
Provides-Extra: pge
|
|
36
|
+
Requires-Dist: httpx>=0.27; extra == 'pge'
|
|
37
|
+
Provides-Extra: secrets
|
|
38
|
+
Requires-Dist: keyring>=25; extra == 'secrets'
|
|
39
|
+
Provides-Extra: statements
|
|
40
|
+
Requires-Dist: pypdf>=4.0; extra == 'statements'
|
|
41
|
+
Provides-Extra: web
|
|
42
|
+
Requires-Dist: fastapi>=0.110; extra == 'web'
|
|
43
|
+
Requires-Dist: uvicorn[standard]>=0.29; extra == 'web'
|
|
44
|
+
Description-Content-Type: text/markdown
|
|
45
|
+
|
|
46
|
+
# TariffKit
|
|
47
|
+
|
|
48
|
+
An offline electricity tariff engine for pricing, billing, and energy-system
|
|
49
|
+
integrations. The first data provider supports PG&E residential rate plans under
|
|
50
|
+
**NEM 3.0 / the Net Billing Tariff (NBT)**; the package identity is deliberately
|
|
51
|
+
not tied to one utility or tariff program.
|
|
52
|
+
|
|
53
|
+
Three schedules are vendored: **E-ELEC** (Electric Home), **E-TOU-C**
|
|
54
|
+
(Time-of-Use, peak 4–9 p.m. every day), and **EV2-A** (Home Charging).
|
|
55
|
+
|
|
56
|
+
Under NBT your export credit is not a time-of-use schedule: it is an hourly
|
|
57
|
+
Avoided Cost Calculator value that swings from about $0.06/kWh at midday to
|
|
58
|
+
about $1.19/kWh on an August evening. Knowing what a kWh is worth right now, and
|
|
59
|
+
what it will be worth over the next two days, is the input to every useful
|
|
60
|
+
solar-and-battery dispatch decision.
|
|
61
|
+
|
|
62
|
+
```python
|
|
63
|
+
from tariffkit import RateEngine
|
|
64
|
+
|
|
65
|
+
engine = RateEngine()
|
|
66
|
+
point = engine.price_now()
|
|
67
|
+
|
|
68
|
+
print(point.import_price.total) # $/kWh to draw from the grid
|
|
69
|
+
print(point.export_price.total) # $/kWh earned by exporting
|
|
70
|
+
print(point.spread) # positive => exporting beats self-consuming
|
|
71
|
+
|
|
72
|
+
curve = engine.forecast(hours=48)
|
|
73
|
+
for hour in curve.best_export_hours(3):
|
|
74
|
+
print(hour.start, hour.export_price.total)
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
## Why it works offline
|
|
78
|
+
|
|
79
|
+
PG&E publishes 20 years of hourly export rates per vintage, as CPUC Resolution
|
|
80
|
+
E-5301 requires (roughly 40 MB of CSV per vintage). But that file is a lossless
|
|
81
|
+
expansion of a 576-cell matrix per year (12 months × 2 day types × 24 hours) per
|
|
82
|
+
component. `tariffkit` collapses it at build time, verifying losslessness cell by
|
|
83
|
+
cell, so the entire five-vintage dataset ships inside the wheel at **268 KiB**
|
|
84
|
+
and every lookup is a few list indexes.
|
|
85
|
+
|
|
86
|
+
The retail side is similar: these schedules' period boundaries are identical every day
|
|
87
|
+
of the week including holidays and do not shift by season, so an import price is
|
|
88
|
+
fully determined by `(season, hour)`.
|
|
89
|
+
|
|
90
|
+
Nothing here touches the network at runtime.
|
|
91
|
+
|
|
92
|
+
## Documentation
|
|
93
|
+
|
|
94
|
+
| | |
|
|
95
|
+
|---|---|
|
|
96
|
+
| [Configuration](docs/configuration.md) | Settings, CCA setup, reading your bill |
|
|
97
|
+
| [Library](docs/library.md) | Embedding in Python |
|
|
98
|
+
| [Named account profiles](docs/accounts.md) | Tracking a changing service agreement over time, importing PG&E statements |
|
|
99
|
+
| [Bill calculator](docs/billing.md) | Computing a cycle from interval meter data |
|
|
100
|
+
| [MQTT](docs/mqtt.md) | Publishing, with Home Assistant discovery |
|
|
101
|
+
| [REST API](docs/web.md) | HTTP service |
|
|
102
|
+
| [Home Assistant](docs/home-assistant.md) | Custom component, Energy dashboard, account history, response actions, opt-in Predbat |
|
|
103
|
+
| [Home Assistant quality checklist](docs/home-assistant-quality.md) | Self-assessment against the Integration Quality Scale, with exemptions |
|
|
104
|
+
| [Containers](docs/containers.md) | Local Home Assistant development stack and API/MQTT deployment proposal |
|
|
105
|
+
| [Maintaining rate data](docs/data.md) | Regenerating export rates, updating the retail tariff and CCA cards |
|
|
106
|
+
| [Packaging strategy](docs/packaging_strategy.md) | Architecture decision, boundaries, and release model |
|
|
107
|
+
| [Release procedure](docs/releases.md) | Versioning, Trusted Publishing, verification, and recovery |
|
|
108
|
+
|
|
109
|
+
## Works with
|
|
110
|
+
|
|
111
|
+
Prices are published in the shapes these already read, via either the custom
|
|
112
|
+
component or the MQTT publisher — no template plumbing on your side. The two
|
|
113
|
+
surfaces differ for EMHASS and Predbat: MQTT always publishes their
|
|
114
|
+
attributes, while the custom component asks for a window on demand and keeps
|
|
115
|
+
Predbat opt-in.
|
|
116
|
+
|
|
117
|
+
| | Custom component | MQTT |
|
|
118
|
+
|---|---|---|
|
|
119
|
+
| **Home Assistant Energy dashboard** | Import/export price entities | Import/export price entities |
|
|
120
|
+
| **EMHASS** | `tariffkit.get_emhass_forecast` action, called with any window | `load_cost_forecast` / `prod_price_forecast` attributes, always published |
|
|
121
|
+
| **Predbat** | `raw_today` / `raw_tomorrow` attributes, only once enabled in options | `raw_today` / `raw_tomorrow` attributes, always published |
|
|
122
|
+
|
|
123
|
+
See [docs/home-assistant.md](docs/home-assistant.md) and
|
|
124
|
+
[docs/mqtt.md](docs/mqtt.md) for setup of each.
|
|
125
|
+
|
|
126
|
+
## Install
|
|
127
|
+
|
|
128
|
+
```bash
|
|
129
|
+
pip install tariffkit # core, zero dependencies
|
|
130
|
+
pip install 'tariffkit[mqtt]' # + MQTT publisher with Home Assistant discovery
|
|
131
|
+
pip install 'tariffkit[web]' # + FastAPI service
|
|
132
|
+
pip install 'tariffkit[secrets]' # + OS keyring credential storage
|
|
133
|
+
pip install 'tariffkit[statements]' # + reading local PG&E statement PDFs
|
|
134
|
+
pip install 'tariffkit[all]'
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
## CLI
|
|
138
|
+
|
|
139
|
+
```bash
|
|
140
|
+
tariffkit now # current import/export price
|
|
141
|
+
tariffkit forecast --hours 48 # the upcoming curve
|
|
142
|
+
tariffkit forecast --format json # machine-readable
|
|
143
|
+
tariffkit mqtt --broker 192.168.1.100 # publish hourly, with HA discovery
|
|
144
|
+
tariffkit serve # REST API on :8000
|
|
145
|
+
tariffkit bill intervals.csv # compute a cycle from meter data
|
|
146
|
+
tariffkit info # which data is loaded, and from where
|
|
147
|
+
tariffkit account init home # track a service agreement's history
|
|
148
|
+
tariffkit account source home show ha # inspect profile grid-import/export entities
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
## Configuration
|
|
152
|
+
|
|
153
|
+
Defaults target a PG&E-bundled residential customer. Point it at your own
|
|
154
|
+
service agreement via `~/.config/tariffkit/config.toml`:
|
|
155
|
+
|
|
156
|
+
```toml
|
|
157
|
+
supplier = "bundled" # or "cca"
|
|
158
|
+
interconnection_year = 2026 # selects the NBT vintage and ACC Plus row
|
|
159
|
+
pto_date = "2026-06-03" # starts the nine-year rate lock
|
|
160
|
+
acc_plus_segment = "residential"
|
|
161
|
+
base_services_charge_tier = 3
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
`TARIFFKIT_*` environment variables override any file setting.
|
|
165
|
+
Long-lived PG&E, Home Assistant, InfluxDB, and MQTT credentials can be stored
|
|
166
|
+
outside that file with `tariffkit credentials set`; see
|
|
167
|
+
[Configuration](docs/configuration.md#credentials).
|
|
168
|
+
|
|
169
|
+
### What the numbers include
|
|
170
|
+
|
|
171
|
+
- **Import price** is the marginal per-kWh cost: generation + distribution for
|
|
172
|
+
the season and period, plus the flat riders. The AB 205 Base Services Charge
|
|
173
|
+
is a fixed $/day amount and is deliberately *excluded*: folding it into a
|
|
174
|
+
$/kWh figure would corrupt any marginal dispatch decision. Read it separately
|
|
175
|
+
via `engine.daily_fixed_charge()`.
|
|
176
|
+
- **Export credit** is the generation component plus the delivery component,
|
|
177
|
+
plus your ACC Plus adder. Values past your nine-year lock are still returned
|
|
178
|
+
but flagged `locked=False`; PG&E publishes them for illustration only.
|
|
179
|
+
|
|
180
|
+
### Edge cases in PG&E's published data
|
|
181
|
+
|
|
182
|
+
Found by round-tripping the vendored matrices against all 1.75 million source
|
|
183
|
+
rows. The library handles each; they are documented because they are surprising.
|
|
184
|
+
|
|
185
|
+
- **The autumn DST hour.** The fall-back day has 25 real hours but only 24 rate
|
|
186
|
+
labels. PG&E gives the repeated 01:00 PST the `HS2` label, so it is priced as
|
|
187
|
+
2am. Pricing it by wall-clock hour returns the wrong value for that hour.
|
|
188
|
+
- **Far-future drift.** From 2036 onward PG&E's own hour labels stop tracking
|
|
189
|
+
Pacific daylight time, and NBT25/26/00 duplicate some holidays onto the
|
|
190
|
+
following day. Each vintage records the last year verified exact; prices past
|
|
191
|
+
it are returned with `exact=False`. Every year inside a nine-year rate lock is
|
|
192
|
+
exact, and those years are already published as illustrative only.
|
|
193
|
+
- **Vintages disagree about holidays** in those same late years, so the holiday
|
|
194
|
+
calendar used for a lookup is the one embedded in that vintage's own file.
|
|
195
|
+
- **Only the June 2026 tariff sheets are vendored**, for each of the three
|
|
196
|
+
schedules. Earlier timestamps raise rather than silently back-dating current
|
|
197
|
+
rates onto an older billing period.
|
|
198
|
+
- **E-TOU-C's baseline credit is not in the marginal price.** It applies to the
|
|
199
|
+
first N kWh of a cycle, which is a quantity rather than a time, so `price_at`
|
|
200
|
+
reports it as `baseline_credit` and the billing engine applies it.
|
|
201
|
+
|
|
202
|
+
### CCA customers
|
|
203
|
+
|
|
204
|
+
If a Community Choice Aggregator supplies your generation, PG&E still delivers,
|
|
205
|
+
and under NEM 3.0 you receive **only the delivery component** of the export
|
|
206
|
+
credit from PG&E; generation compensation comes from the CCA.
|
|
207
|
+
|
|
208
|
+
An MCE rate card is vendored (generation by season/period, the Cost Relief
|
|
209
|
+
Credit, Deep Green premium, and the 10% Solar Bonus Credit):
|
|
210
|
+
|
|
211
|
+
```toml
|
|
212
|
+
supplier = "cca"
|
|
213
|
+
|
|
214
|
+
[cca]
|
|
215
|
+
name = "MCE"
|
|
216
|
+
rate_card = "mce"
|
|
217
|
+
pcia_rate = 0.03476 # $/kWh, from your bill
|
|
218
|
+
franchise_fee_surcharge = 0.00042 # $/kWh, from your bill
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
For other CCAs, supply `generation_rates` directly; see
|
|
222
|
+
[docs/configuration.md](docs/configuration.md). Until generation rates and a
|
|
223
|
+
franchise fee are configured, CCA mode returns delivery-only prices flagged
|
|
224
|
+
`complete = False` rather than quietly understating your rates.
|
|
225
|
+
|
|
226
|
+
Note that a CCA customer's PCIA is a **charge**, while a bundled customer's is a
|
|
227
|
+
**credit**, so CCA service can cost several cents per kWh more on import.
|
|
228
|
+
|
|
229
|
+
## Keeping rates current
|
|
230
|
+
|
|
231
|
+
Every vendored dataset is regenerated from the document that publishes it, by
|
|
232
|
+
`tools.regen`. Nothing is hand-transcribed and nothing is hand-edited.
|
|
233
|
+
|
|
234
|
+
```bash
|
|
235
|
+
python -m tools.regen # rebuild every dataset
|
|
236
|
+
python -m tools.regen --check # exit 1 if a publisher moved
|
|
237
|
+
python -m tools.regen tariff --for-date 2025-12-15 # rebuild a superseded vintage
|
|
238
|
+
python -m tools.regen.export --download # the 843 MB export-rate archive
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
Export matrices come from PG&E's CSV archive, which is large enough to have its
|
|
242
|
+
own entry point; everything else comes from a published PDF. Nothing is written
|
|
243
|
+
unless the rendered file survives being read back by the library code that will
|
|
244
|
+
consume it, so a generator that drifts from the schema fails instead of shipping.
|
|
245
|
+
|
|
246
|
+
A weekly CI job runs `--check`, so a rate change surfaces as a failing build
|
|
247
|
+
rather than as silent drift. Export files are updated by **October 1** of any
|
|
248
|
+
year the CPUC adopts a new Avoided Cost Calculator; retail rates change more
|
|
249
|
+
often, via advice letters — three times in the first half of 2026 alone — which
|
|
250
|
+
is why a superseded vintage can be rebuilt from the filing that adopted it. See
|
|
251
|
+
[docs/data.md](docs/data.md).
|
|
252
|
+
|
|
253
|
+
## Data sources
|
|
254
|
+
|
|
255
|
+
See [docs/data.md](docs/data.md#data-sources) for the source of every vendored
|
|
256
|
+
table and why OpenEI's URDB is deliberately not used.
|
|
257
|
+
|
|
258
|
+
## License
|
|
259
|
+
|
|
260
|
+
MIT
|