tariffkit 0.2.2__tar.gz → 0.2.3__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.2 → tariffkit-0.2.3}/CHANGELOG.md +21 -1
- {tariffkit-0.2.2 → tariffkit-0.2.3}/PKG-INFO +2 -2
- {tariffkit-0.2.2 → tariffkit-0.2.3}/README.md +1 -1
- {tariffkit-0.2.2 → tariffkit-0.2.3}/pyproject.toml +1 -1
- {tariffkit-0.2.2 → tariffkit-0.2.3}/src/tariffkit/__init__.py +2 -0
- {tariffkit-0.2.2 → tariffkit-0.2.3}/src/tariffkit/cca.py +7 -1
- {tariffkit-0.2.2 → tariffkit-0.2.3}/src/tariffkit/config.py +4 -3
- {tariffkit-0.2.2 → tariffkit-0.2.3}/src/tariffkit/data/cca/mce/2023-01-01.toml +1 -1
- {tariffkit-0.2.2 → tariffkit-0.2.3}/src/tariffkit/data/cca/mce/2026-04-01.toml +1 -1
- {tariffkit-0.2.2 → tariffkit-0.2.3}/src/tariffkit/data/export/pge/acc_plus/2023-04-15.toml +2 -6
- {tariffkit-0.2.2 → tariffkit-0.2.3}/src/tariffkit/data/nsc/pge.toml +2 -2
- {tariffkit-0.2.2 → tariffkit-0.2.3}/src/tariffkit/data/tariff/pge/eelec/2025-01-01.toml +1 -1
- {tariffkit-0.2.2 → tariffkit-0.2.3}/src/tariffkit/data/tariff/pge/eelec/2025-03-01.toml +1 -1
- {tariffkit-0.2.2 → tariffkit-0.2.3}/src/tariffkit/data/tariff/pge/eelec/2025-09-01.toml +1 -1
- {tariffkit-0.2.2 → tariffkit-0.2.3}/src/tariffkit/data/tariff/pge/eelec/2026-01-01.toml +1 -1
- {tariffkit-0.2.2 → tariffkit-0.2.3}/src/tariffkit/data/tariff/pge/eelec/2026-03-01.toml +2 -2
- {tariffkit-0.2.2 → tariffkit-0.2.3}/src/tariffkit/data/tariff/pge/etouc/2025-01-01.toml +1 -1
- {tariffkit-0.2.2 → tariffkit-0.2.3}/src/tariffkit/data/tariff/pge/etouc/2025-03-01.toml +1 -1
- {tariffkit-0.2.2 → tariffkit-0.2.3}/src/tariffkit/data/tariff/pge/etouc/2025-09-01.toml +1 -1
- {tariffkit-0.2.2 → tariffkit-0.2.3}/src/tariffkit/data/tariff/pge/etouc/2026-01-01.toml +1 -1
- {tariffkit-0.2.2 → tariffkit-0.2.3}/src/tariffkit/data/tariff/pge/etouc/2026-03-01.toml +6 -4
- {tariffkit-0.2.2 → tariffkit-0.2.3}/src/tariffkit/data/tariff/pge/ev2a/2025-01-01.toml +1 -1
- {tariffkit-0.2.2 → tariffkit-0.2.3}/src/tariffkit/data/tariff/pge/ev2a/2025-03-01.toml +1 -1
- {tariffkit-0.2.2 → tariffkit-0.2.3}/src/tariffkit/data/tariff/pge/ev2a/2025-09-01.toml +1 -1
- {tariffkit-0.2.2 → tariffkit-0.2.3}/src/tariffkit/data/tariff/pge/ev2a/2026-01-01.toml +1 -1
- {tariffkit-0.2.2 → tariffkit-0.2.3}/src/tariffkit/data/tariff/pge/ev2a/2026-03-01.toml +2 -2
- {tariffkit-0.2.2 → tariffkit-0.2.3}/src/tariffkit/engine.py +6 -2
- {tariffkit-0.2.2 → tariffkit-0.2.3}/src/tariffkit/export/nbt.py +5 -4
- {tariffkit-0.2.2 → tariffkit-0.2.3}/src/tariffkit/models.py +34 -0
- {tariffkit-0.2.2 → tariffkit-0.2.3}/src/tariffkit/mqtt/discovery.py +5 -2
- {tariffkit-0.2.2 → tariffkit-0.2.3}/src/tariffkit/tariff/retail.py +11 -6
- {tariffkit-0.2.2 → tariffkit-0.2.3}/tests/test_eelec.py +10 -6
- {tariffkit-0.2.2 → tariffkit-0.2.3}/tests/test_engine.py +26 -2
- {tariffkit-0.2.2 → tariffkit-0.2.3}/tests/test_integrations.py +4 -0
- {tariffkit-0.2.2 → tariffkit-0.2.3}/tests/test_mce.py +6 -2
- {tariffkit-0.2.2 → tariffkit-0.2.3}/tests/test_schedules.py +12 -7
- {tariffkit-0.2.2 → tariffkit-0.2.3}/.gitignore +0 -0
- {tariffkit-0.2.2 → tariffkit-0.2.3}/LICENSE +0 -0
- {tariffkit-0.2.2 → tariffkit-0.2.3}/src/tariffkit/account/__init__.py +0 -0
- {tariffkit-0.2.2 → tariffkit-0.2.3}/src/tariffkit/account/cli.py +0 -0
- {tariffkit-0.2.2 → tariffkit-0.2.3}/src/tariffkit/account/errors.py +0 -0
- {tariffkit-0.2.2 → tariffkit-0.2.3}/src/tariffkit/account/model.py +0 -0
- {tariffkit-0.2.2 → tariffkit-0.2.3}/src/tariffkit/account/rates.py +0 -0
- {tariffkit-0.2.2 → tariffkit-0.2.3}/src/tariffkit/account/repository.py +0 -0
- {tariffkit-0.2.2 → tariffkit-0.2.3}/src/tariffkit/billing/__init__.py +0 -0
- {tariffkit-0.2.2 → tariffkit-0.2.3}/src/tariffkit/billing/engine.py +0 -0
- {tariffkit-0.2.2 → tariffkit-0.2.3}/src/tariffkit/billing/ledger.py +0 -0
- {tariffkit-0.2.2 → tariffkit-0.2.3}/src/tariffkit/billing/models.py +0 -0
- {tariffkit-0.2.2 → tariffkit-0.2.3}/src/tariffkit/billing/netting.py +0 -0
- {tariffkit-0.2.2 → tariffkit-0.2.3}/src/tariffkit/billing/trueup.py +0 -0
- {tariffkit-0.2.2 → tariffkit-0.2.3}/src/tariffkit/cli.py +0 -0
- {tariffkit-0.2.2 → tariffkit-0.2.3}/src/tariffkit/data/__init__.py +0 -0
- {tariffkit-0.2.2 → tariffkit-0.2.3}/src/tariffkit/data/export/pge/nbt00.json.gz +0 -0
- {tariffkit-0.2.2 → tariffkit-0.2.3}/src/tariffkit/data/export/pge/nbt23.json.gz +0 -0
- {tariffkit-0.2.2 → tariffkit-0.2.3}/src/tariffkit/data/export/pge/nbt24.json.gz +0 -0
- {tariffkit-0.2.2 → tariffkit-0.2.3}/src/tariffkit/data/export/pge/nbt25.json.gz +0 -0
- {tariffkit-0.2.2 → tariffkit-0.2.3}/src/tariffkit/data/export/pge/nbt26.json.gz +0 -0
- {tariffkit-0.2.2 → tariffkit-0.2.3}/src/tariffkit/data/holidays.toml +0 -0
- {tariffkit-0.2.2 → tariffkit-0.2.3}/src/tariffkit/data/manifest.json +0 -0
- {tariffkit-0.2.2 → tariffkit-0.2.3}/src/tariffkit/data/tax/ca_energy_resources/2025-01-01.toml +0 -0
- {tariffkit-0.2.2 → tariffkit-0.2.3}/src/tariffkit/data/tax/ca_energy_resources/2026-01-01.toml +0 -0
- {tariffkit-0.2.2 → tariffkit-0.2.3}/src/tariffkit/data/versioned.py +0 -0
- {tariffkit-0.2.2 → tariffkit-0.2.3}/src/tariffkit/errors.py +0 -0
- {tariffkit-0.2.2 → tariffkit-0.2.3}/src/tariffkit/export/__init__.py +0 -0
- {tariffkit-0.2.2 → tariffkit-0.2.3}/src/tariffkit/interop/__init__.py +0 -0
- {tariffkit-0.2.2 → tariffkit-0.2.3}/src/tariffkit/interop/emhass.py +0 -0
- {tariffkit-0.2.2 → tariffkit-0.2.3}/src/tariffkit/interop/predbat.py +0 -0
- {tariffkit-0.2.2 → tariffkit-0.2.3}/src/tariffkit/interop/slots.py +0 -0
- {tariffkit-0.2.2 → tariffkit-0.2.3}/src/tariffkit/mqtt/__init__.py +0 -0
- {tariffkit-0.2.2 → tariffkit-0.2.3}/src/tariffkit/mqtt/publisher.py +0 -0
- {tariffkit-0.2.2 → tariffkit-0.2.3}/src/tariffkit/providers/__init__.py +0 -0
- {tariffkit-0.2.2 → tariffkit-0.2.3}/src/tariffkit/providers/pge/__init__.py +0 -0
- {tariffkit-0.2.2 → tariffkit-0.2.3}/src/tariffkit/providers/pge/reconcile.py +0 -0
- {tariffkit-0.2.2 → tariffkit-0.2.3}/src/tariffkit/providers/pge/statements/__init__.py +0 -0
- {tariffkit-0.2.2 → tariffkit-0.2.3}/src/tariffkit/providers/pge/statements/errors.py +0 -0
- {tariffkit-0.2.2 → tariffkit-0.2.3}/src/tariffkit/providers/pge/statements/model.py +0 -0
- {tariffkit-0.2.2 → tariffkit-0.2.3}/src/tariffkit/providers/pge/statements/ocr.py +0 -0
- {tariffkit-0.2.2 → tariffkit-0.2.3}/src/tariffkit/providers/pge/statements/parse.py +0 -0
- {tariffkit-0.2.2 → tariffkit-0.2.3}/src/tariffkit/py.typed +0 -0
- {tariffkit-0.2.2 → tariffkit-0.2.3}/src/tariffkit/secrets.py +0 -0
- {tariffkit-0.2.2 → tariffkit-0.2.3}/src/tariffkit/sources/__init__.py +0 -0
- {tariffkit-0.2.2 → tariffkit-0.2.3}/src/tariffkit/sources/greenbutton.py +0 -0
- {tariffkit-0.2.2 → tariffkit-0.2.3}/src/tariffkit/sources/homeassistant.py +0 -0
- {tariffkit-0.2.2 → tariffkit-0.2.3}/src/tariffkit/sources/influx.py +0 -0
- {tariffkit-0.2.2 → tariffkit-0.2.3}/src/tariffkit/sources/pge.py +0 -0
- {tariffkit-0.2.2 → tariffkit-0.2.3}/src/tariffkit/tariff/__init__.py +0 -0
- {tariffkit-0.2.2 → tariffkit-0.2.3}/src/tariffkit/timeutil.py +0 -0
- {tariffkit-0.2.2 → tariffkit-0.2.3}/src/tariffkit/web/__init__.py +0 -0
- {tariffkit-0.2.2 → tariffkit-0.2.3}/src/tariffkit/web/app.py +0 -0
- {tariffkit-0.2.2 → tariffkit-0.2.3}/tests/conftest.py +0 -0
- {tariffkit-0.2.2 → tariffkit-0.2.3}/tests/fixtures/nbt_golden_rows.jsonl +0 -0
- {tariffkit-0.2.2 → tariffkit-0.2.3}/tests/fixtures/statements/synthetic_cca_ratechange.txt +0 -0
- {tariffkit-0.2.2 → tariffkit-0.2.3}/tests/test_account.py +0 -0
- {tariffkit-0.2.2 → tariffkit-0.2.3}/tests/test_account_cli.py +0 -0
- {tariffkit-0.2.2 → tariffkit-0.2.3}/tests/test_billing.py +0 -0
- {tariffkit-0.2.2 → tariffkit-0.2.3}/tests/test_ev2a.py +0 -0
- {tariffkit-0.2.2 → tariffkit-0.2.3}/tests/test_export_golden.py +0 -0
- {tariffkit-0.2.2 → tariffkit-0.2.3}/tests/test_interop.py +0 -0
- {tariffkit-0.2.2 → tariffkit-0.2.3}/tests/test_ledger.py +0 -0
- {tariffkit-0.2.2 → tariffkit-0.2.3}/tests/test_mqtt_publisher.py +0 -0
- {tariffkit-0.2.2 → tariffkit-0.2.3}/tests/test_pge_reconcile.py +0 -0
- {tariffkit-0.2.2 → tariffkit-0.2.3}/tests/test_pge_statements.py +0 -0
- {tariffkit-0.2.2 → tariffkit-0.2.3}/tests/test_private_config.py +0 -0
- {tariffkit-0.2.2 → tariffkit-0.2.3}/tests/test_sources_greenbutton.py +0 -0
- {tariffkit-0.2.2 → tariffkit-0.2.3}/tests/test_sources_ha.py +0 -0
- {tariffkit-0.2.2 → tariffkit-0.2.3}/tests/test_sources_influx.py +0 -0
- {tariffkit-0.2.2 → tariffkit-0.2.3}/tests/test_sources_pge.py +0 -0
- {tariffkit-0.2.2 → tariffkit-0.2.3}/tests/test_trueup.py +0 -0
- {tariffkit-0.2.2 → tariffkit-0.2.3}/tests/test_versioned.py +0 -0
|
@@ -5,6 +5,25 @@ All notable changes to this project are documented here. This project follows
|
|
|
5
5
|
|
|
6
6
|
## [Unreleased]
|
|
7
7
|
|
|
8
|
+
## [0.2.3] - 2026-08-17
|
|
9
|
+
|
|
10
|
+
### Changed
|
|
11
|
+
- Pacific Gas and Electric now has the unambiguous machine identifier
|
|
12
|
+
`pacific_gas_and_electric`, while Home Assistant and MQTT present `PG&E` or
|
|
13
|
+
the full company name. The separate `pge` identifier correctly means Portland
|
|
14
|
+
General Electric and is recognized but explicitly unsupported for pricing,
|
|
15
|
+
preventing it from ever selecting California tariff data.
|
|
16
|
+
- Home Assistant now labels the export-minus-import calculation explicitly,
|
|
17
|
+
translates time-of-use states for display, and presents the forecast horizon
|
|
18
|
+
as **Rates Available Through**. Forecast metadata and a new **Rate Data
|
|
19
|
+
Status** entity are grouped under diagnostics, where PTO date, export lock
|
|
20
|
+
end, NBT vintage, tariff provenance, source, and quality flags explain the
|
|
21
|
+
active rates without crowding the primary price controls.
|
|
22
|
+
|
|
23
|
+
### Fixed
|
|
24
|
+
- The PyPI project page now loads the TariffKit banner from an absolute URL
|
|
25
|
+
instead of an unresolved repository-relative path.
|
|
26
|
+
|
|
8
27
|
## [0.2.2] - 2026-08-16
|
|
9
28
|
|
|
10
29
|
### Added
|
|
@@ -221,7 +240,8 @@ Initial release.
|
|
|
221
240
|
- Holiday calendars are extracted per vintage from the source data rather than
|
|
222
241
|
recomputed, because the vintage files disagree in far-future years.
|
|
223
242
|
|
|
224
|
-
[Unreleased]: https://github.com/eman/tariffkit/compare/v0.2.
|
|
243
|
+
[Unreleased]: https://github.com/eman/tariffkit/compare/v0.2.3...HEAD
|
|
244
|
+
[0.2.3]: https://github.com/eman/tariffkit/releases/tag/v0.2.3
|
|
225
245
|
[0.2.2]: https://github.com/eman/tariffkit/releases/tag/v0.2.2
|
|
226
246
|
[0.2.1]: https://github.com/eman/tariffkit/releases/tag/v0.2.1
|
|
227
247
|
[0.2.0]: https://github.com/eman/tariffkit/releases/tag/v0.2.0
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: tariffkit
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.3
|
|
4
4
|
Summary: Offline electricity tariff pricing, billing, and integration tools
|
|
5
5
|
Project-URL: Homepage, https://github.com/eman/tariffkit
|
|
6
6
|
Project-URL: Documentation, https://github.com/eman/tariffkit#documentation
|
|
@@ -44,7 +44,7 @@ Requires-Dist: uvicorn[standard]>=0.29; extra == 'web'
|
|
|
44
44
|
Description-Content-Type: text/markdown
|
|
45
45
|
|
|
46
46
|
<p align="center">
|
|
47
|
-
<img src="images/banner.svg" alt="TariffKit — electricity pricing, billing, and energy integrations" width="100%">
|
|
47
|
+
<img src="https://raw.githubusercontent.com/eman/tariffkit/main/images/banner.svg" alt="TariffKit — electricity pricing, billing, and energy integrations" width="100%">
|
|
48
48
|
</p>
|
|
49
49
|
|
|
50
50
|
# TariffKit
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<p align="center">
|
|
2
|
-
<img src="images/banner.svg" alt="TariffKit — electricity pricing, billing, and energy integrations" width="100%">
|
|
2
|
+
<img src="https://raw.githubusercontent.com/eman/tariffkit/main/images/banner.svg" alt="TariffKit — electricity pricing, billing, and energy integrations" width="100%">
|
|
3
3
|
</p>
|
|
4
4
|
|
|
5
5
|
# TariffKit
|
|
@@ -25,6 +25,7 @@ from .models import (
|
|
|
25
25
|
Season,
|
|
26
26
|
Supplier,
|
|
27
27
|
TouPeriod,
|
|
28
|
+
Utility,
|
|
28
29
|
)
|
|
29
30
|
from .timeutil import PACIFIC, DayType, now_pacific
|
|
30
31
|
|
|
@@ -47,6 +48,7 @@ __all__ = [
|
|
|
47
48
|
"Supplier",
|
|
48
49
|
"TariffKitError",
|
|
49
50
|
"TouPeriod",
|
|
51
|
+
"Utility",
|
|
50
52
|
"__version__",
|
|
51
53
|
"now_pacific",
|
|
52
54
|
]
|
|
@@ -15,6 +15,7 @@ from typing import Any
|
|
|
15
15
|
|
|
16
16
|
from .data import versioned
|
|
17
17
|
from .errors import ConfigError, DataError
|
|
18
|
+
from .models import Utility
|
|
18
19
|
|
|
19
20
|
|
|
20
21
|
@dataclass(frozen=True, slots=True)
|
|
@@ -115,7 +116,12 @@ def load_rate_card(provider: str, on: date) -> CcaRateCard:
|
|
|
115
116
|
) from exc
|
|
116
117
|
if version.raw.get("schema") != 1:
|
|
117
118
|
raise DataError(f"{provider}: unsupported rate card schema {version.raw.get('schema')}")
|
|
118
|
-
|
|
119
|
+
raw = dict(version.raw)
|
|
120
|
+
try:
|
|
121
|
+
raw["utility"] = Utility(raw["utility"]).value
|
|
122
|
+
except (KeyError, ValueError) as exc:
|
|
123
|
+
raise DataError(f"{provider}: invalid utility identifier") from exc
|
|
124
|
+
return CcaRateCard(provider=str(raw["provider"]), raw=raw)
|
|
119
125
|
|
|
120
126
|
|
|
121
127
|
def available_rate_cards() -> tuple[str, ...]:
|
|
@@ -11,7 +11,7 @@ from pathlib import Path
|
|
|
11
11
|
from typing import Any, Literal
|
|
12
12
|
|
|
13
13
|
from .errors import ConfigError
|
|
14
|
-
from .models import Supplier
|
|
14
|
+
from .models import Supplier, Utility
|
|
15
15
|
|
|
16
16
|
AccPlusSegment = Literal["residential", "residential_low_income", "none"]
|
|
17
17
|
Discount = Literal["none", "care", "fera"]
|
|
@@ -80,7 +80,7 @@ class CcaConfig:
|
|
|
80
80
|
class Config:
|
|
81
81
|
"""Everything needed to price a kWh for one service agreement."""
|
|
82
82
|
|
|
83
|
-
utility:
|
|
83
|
+
utility: Utility = Utility.PACIFIC_GAS_AND_ELECTRIC
|
|
84
84
|
tariff: str = "E-ELEC"
|
|
85
85
|
supplier: Supplier = Supplier.BUNDLED
|
|
86
86
|
|
|
@@ -117,6 +117,7 @@ class Config:
|
|
|
117
117
|
nsc_rate: float | None = None
|
|
118
118
|
|
|
119
119
|
def __post_init__(self) -> None:
|
|
120
|
+
object.__setattr__(self, "utility", Utility(self.utility))
|
|
120
121
|
# Coerce the string forms of the enums. Supplier is a StrEnum, so a
|
|
121
122
|
# plain "cca" compares equal to Supplier.CCA but is not it -- and every
|
|
122
123
|
# branch that matters tests identity. Constructing Config directly with
|
|
@@ -195,7 +196,7 @@ class Config:
|
|
|
195
196
|
def to_dict(self) -> dict[str, Any]:
|
|
196
197
|
"""A JSON-compatible representation suitable for API request bodies."""
|
|
197
198
|
data: dict[str, Any] = {
|
|
198
|
-
"utility": self.utility,
|
|
199
|
+
"utility": self.utility.value,
|
|
199
200
|
"tariff": self.tariff,
|
|
200
201
|
"supplier": self.supplier.value,
|
|
201
202
|
"interconnection_year": self.interconnection_year,
|
|
@@ -49,7 +49,7 @@
|
|
|
49
49
|
schema = 1
|
|
50
50
|
provider = "MCE"
|
|
51
51
|
name = "Marin Clean Energy"
|
|
52
|
-
utility = "
|
|
52
|
+
utility = "pacific_gas_and_electric"
|
|
53
53
|
#: MCE prices each PG&E schedule separately, so a rate card is keyed by schedule
|
|
54
54
|
#: rather than tied to one. Slugs match the tariff data directories.
|
|
55
55
|
schedules = ["E-ELEC", "E-TOU-C", "EV2-A"]
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# ACC Plus adder, Pacific Gas and Electric Schedule NBT.
|
|
1
|
+
# ACC Plus adder, Pacific Gas and Electric Company Schedule NBT.
|
|
2
2
|
#
|
|
3
3
|
# Source: https://www.pge.com/tariffs/assets/pdf/tariffbook/ELEC_SCHEDS_NBT.pdf
|
|
4
4
|
#
|
|
@@ -18,13 +18,9 @@
|
|
|
18
18
|
# closure.
|
|
19
19
|
|
|
20
20
|
schema = 1
|
|
21
|
-
# When this version of the adder table took force. The Net Billing Tariff opened
|
|
22
|
-
# on 2023-04-15 and the five-year step-down has been published unchanged since,
|
|
23
|
-
# so there is one vintage -- but it is dated, because a revision must be able to
|
|
24
|
-
# sit beside it rather than overwrite it.
|
|
25
21
|
effective = "2023-04-15"
|
|
26
22
|
unit = "USD/kWh"
|
|
27
|
-
source = "Pacific Gas and Electric Schedule NBT"
|
|
23
|
+
source = "Pacific Gas and Electric Company Schedule NBT"
|
|
28
24
|
|
|
29
25
|
[residential]
|
|
30
26
|
2023 = 0.02200
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# Pacific Gas and Electric Net Surplus Compensation (NSC) rates, by true-up month.
|
|
1
|
+
# Pacific Gas and Electric Company Net Surplus Compensation (NSC) rates, by true-up month.
|
|
2
2
|
#
|
|
3
3
|
# Source: https://www.pge.com/assets/pge/docs/clean-energy/solar/AB920-RateTable.pdf
|
|
4
4
|
#
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
# case rather than an edge one.
|
|
29
29
|
|
|
30
30
|
schema = 1
|
|
31
|
-
utility = "
|
|
31
|
+
utility = "pacific_gas_and_electric"
|
|
32
32
|
currency = "USD/kWh"
|
|
33
33
|
source_url = "https://www.pge.com/assets/pge/docs/clean-energy/solar/AB920-RateTable.pdf"
|
|
34
34
|
decision = "D.11-06-016"
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# Pacific Gas and Electric Schedule E-ELEC -- retail time-of-use rates.
|
|
1
|
+
# Pacific Gas and Electric Company Schedule E-ELEC -- retail time-of-use rates.
|
|
2
2
|
#
|
|
3
3
|
# Source: https://www.pge.com/tariffs/assets/pdf/tariffbook/ELEC_SCHEDS_E-ELEC.pdf
|
|
4
4
|
#
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
# makes a silent mis-parse essentially impossible.
|
|
23
23
|
|
|
24
24
|
schema = 1
|
|
25
|
-
utility = "
|
|
25
|
+
utility = "pacific_gas_and_electric"
|
|
26
26
|
tariff = "E-ELEC"
|
|
27
27
|
effective = "2026-03-01"
|
|
28
28
|
advice_letter = "7846-E"
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Pacific Gas and Electric Company Schedule E-TOU-C -- retail time-of-use rates.
|
|
2
2
|
#
|
|
3
3
|
# Source: https://www.pge.com/tariffs/assets/pdf/tariffbook/ELEC_SCHEDS_E-TOU-C.pdf
|
|
4
4
|
#
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
# makes a silent mis-parse essentially impossible.
|
|
27
27
|
|
|
28
28
|
schema = 1
|
|
29
|
-
utility = "
|
|
29
|
+
utility = "pacific_gas_and_electric"
|
|
30
30
|
tariff = "E-TOU-C"
|
|
31
31
|
effective = "2026-03-01"
|
|
32
32
|
advice_letter = "7846-E"
|
|
@@ -193,8 +193,10 @@ drop_components = ["generation", "bundled_pcia"]
|
|
|
193
193
|
2025 = -0.01011
|
|
194
194
|
2026 = -0.01011
|
|
195
195
|
|
|
196
|
-
#
|
|
197
|
-
#
|
|
196
|
+
# Franchise fee surcharge, which a CCA/DA customer pays and a bundled one
|
|
197
|
+
# does not. Published in a separate schedule (E-FFS) and read from it,
|
|
198
|
+
# because carrying it forward would let that schedule be reissued without
|
|
199
|
+
# anything noticing -- it is live rate data on every CCA price.
|
|
198
200
|
[cca.franchise_fee_vintages]
|
|
199
201
|
2009 = 0.00064
|
|
200
202
|
2010 = 0.00061
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# Pacific Gas and Electric Schedule EV2-A -- retail time-of-use rates.
|
|
1
|
+
# Pacific Gas and Electric Company Schedule EV2-A -- retail time-of-use rates.
|
|
2
2
|
#
|
|
3
3
|
# Source: https://www.pge.com/tariffs/assets/pdf/tariffbook/ELEC_SCHEDS_EV2%20(Sch).pdf
|
|
4
4
|
#
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
# makes a silent mis-parse essentially impossible.
|
|
24
24
|
|
|
25
25
|
schema = 1
|
|
26
|
-
utility = "
|
|
26
|
+
utility = "pacific_gas_and_electric"
|
|
27
27
|
tariff = "EV2-A"
|
|
28
28
|
effective = "2026-03-01"
|
|
29
29
|
advice_letter = "7846-E"
|
|
@@ -5,8 +5,9 @@ from __future__ import annotations
|
|
|
5
5
|
from datetime import UTC, datetime, timedelta
|
|
6
6
|
|
|
7
7
|
from .config import Config
|
|
8
|
+
from .errors import DataError
|
|
8
9
|
from .export.nbt import NbtExportRates
|
|
9
|
-
from .models import PriceCurve, PricePoint
|
|
10
|
+
from .models import PriceCurve, PricePoint, Utility
|
|
10
11
|
from .tariff.retail import RetailTariff
|
|
11
12
|
from .timeutil import PACIFIC, hour_floor, now_pacific, to_pacific
|
|
12
13
|
|
|
@@ -21,6 +22,8 @@ class RateEngine:
|
|
|
21
22
|
|
|
22
23
|
def __init__(self, config: Config | None = None) -> None:
|
|
23
24
|
self.config = config or Config()
|
|
25
|
+
if self.config.utility is not Utility.PACIFIC_GAS_AND_ELECTRIC:
|
|
26
|
+
raise DataError(f"{self.config.utility.display_name} pricing is not supported")
|
|
24
27
|
self.tariff = RetailTariff(self.config)
|
|
25
28
|
self.export_rates = NbtExportRates(self.config)
|
|
26
29
|
|
|
@@ -69,7 +72,7 @@ class RateEngine:
|
|
|
69
72
|
snapshot = self.tariff.snapshot_for(when)
|
|
70
73
|
low, high = self.export_rates.covered_years
|
|
71
74
|
return {
|
|
72
|
-
"utility": self.config.utility,
|
|
75
|
+
"utility": self.config.utility.value,
|
|
73
76
|
"tariff": self.config.tariff,
|
|
74
77
|
"supplier": str(self.config.supplier),
|
|
75
78
|
"tariff_effective": snapshot.effective.isoformat(),
|
|
@@ -78,5 +81,6 @@ class RateEngine:
|
|
|
78
81
|
"export_vintage": self.export_rates.vintage,
|
|
79
82
|
"export_years": [low, high],
|
|
80
83
|
"acc_plus": self.export_rates.acc_plus,
|
|
84
|
+
"pto_date": self.config.pto_date.isoformat() if self.config.pto_date else None,
|
|
81
85
|
"lock_end": self.config.lock_end.isoformat() if self.config.lock_end else None,
|
|
82
86
|
}
|
|
@@ -15,7 +15,7 @@ from typing import Any
|
|
|
15
15
|
from ..config import FLOATING_VINTAGE, Config
|
|
16
16
|
from ..data import read_data_json_gz, versioned
|
|
17
17
|
from ..errors import ConfigError, DataError, OutOfRangeError
|
|
18
|
-
from ..models import ExportPrice, Supplier
|
|
18
|
+
from ..models import ExportPrice, Supplier, Utility
|
|
19
19
|
from ..timeutil import MONTHS, DayType, day_type, export_hour
|
|
20
20
|
|
|
21
21
|
#: Where a utility's ACC Plus adder table lives. Keyed by utility rather than
|
|
@@ -32,9 +32,10 @@ def _matrix(vintage: str) -> dict[str, Any]:
|
|
|
32
32
|
return payload
|
|
33
33
|
|
|
34
34
|
|
|
35
|
-
def _acc_plus_table(utility: str, on: date) -> dict[str, Any]:
|
|
36
|
-
|
|
37
|
-
|
|
35
|
+
def _acc_plus_table(utility: Utility | str, on: date) -> dict[str, Any]:
|
|
36
|
+
utility_id = Utility(utility)
|
|
37
|
+
relative = ACC_PLUS_DIR.format(utility=utility_id.data_slug)
|
|
38
|
+
return versioned.load(relative, on, label=f"{utility_id.short_name} ACC Plus").raw
|
|
38
39
|
|
|
39
40
|
|
|
40
41
|
class NbtExportRates:
|
|
@@ -28,6 +28,40 @@ class Supplier(StrEnum):
|
|
|
28
28
|
CCA = "cca"
|
|
29
29
|
|
|
30
30
|
|
|
31
|
+
class Utility(StrEnum):
|
|
32
|
+
"""Stable utility identities used by profiles and API payloads."""
|
|
33
|
+
|
|
34
|
+
PACIFIC_GAS_AND_ELECTRIC = "pacific_gas_and_electric"
|
|
35
|
+
PORTLAND_GENERAL_ELECTRIC = "pge"
|
|
36
|
+
|
|
37
|
+
@property
|
|
38
|
+
def data_slug(self) -> str:
|
|
39
|
+
"""Private filesystem key for this utility's vendored data."""
|
|
40
|
+
match self:
|
|
41
|
+
case Utility.PACIFIC_GAS_AND_ELECTRIC:
|
|
42
|
+
return "pge"
|
|
43
|
+
case Utility.PORTLAND_GENERAL_ELECTRIC:
|
|
44
|
+
return "portland_general_electric"
|
|
45
|
+
|
|
46
|
+
@property
|
|
47
|
+
def short_name(self) -> str:
|
|
48
|
+
"""Recognizable user-facing abbreviation."""
|
|
49
|
+
match self:
|
|
50
|
+
case Utility.PACIFIC_GAS_AND_ELECTRIC:
|
|
51
|
+
return "PG&E"
|
|
52
|
+
case Utility.PORTLAND_GENERAL_ELECTRIC:
|
|
53
|
+
return "PGE"
|
|
54
|
+
|
|
55
|
+
@property
|
|
56
|
+
def display_name(self) -> str:
|
|
57
|
+
"""Full user-facing company name."""
|
|
58
|
+
match self:
|
|
59
|
+
case Utility.PACIFIC_GAS_AND_ELECTRIC:
|
|
60
|
+
return "Pacific Gas and Electric Company"
|
|
61
|
+
case Utility.PORTLAND_GENERAL_ELECTRIC:
|
|
62
|
+
return "Portland General Electric"
|
|
63
|
+
|
|
64
|
+
|
|
31
65
|
@dataclass(frozen=True, slots=True)
|
|
32
66
|
class ImportPrice:
|
|
33
67
|
"""What a kWh drawn from the grid costs, marginally."""
|
|
@@ -8,6 +8,8 @@ from __future__ import annotations
|
|
|
8
8
|
|
|
9
9
|
from typing import Any
|
|
10
10
|
|
|
11
|
+
from ..models import Utility
|
|
12
|
+
|
|
11
13
|
DEVICE_ID = "tariffkit"
|
|
12
14
|
|
|
13
15
|
#: (object_id, name, state topic suffix, icon)
|
|
@@ -19,10 +21,11 @@ SENSORS: tuple[tuple[str, str, str, str], ...] = (
|
|
|
19
21
|
|
|
20
22
|
|
|
21
23
|
def _device(engine_info: dict[str, Any]) -> dict[str, Any]:
|
|
24
|
+
utility = Utility(engine_info.get("utility", Utility.PACIFIC_GAS_AND_ELECTRIC))
|
|
22
25
|
return {
|
|
23
26
|
"identifiers": [DEVICE_ID],
|
|
24
|
-
"name": "
|
|
25
|
-
"manufacturer":
|
|
27
|
+
"name": f"{utility.short_name} Rates",
|
|
28
|
+
"manufacturer": utility.display_name,
|
|
26
29
|
"model": f"{engine_info.get('tariff', 'E-ELEC')} / {engine_info.get('export_vintage', '')}",
|
|
27
30
|
"sw_version": _version(),
|
|
28
31
|
}
|
|
@@ -28,7 +28,7 @@ from typing import Any
|
|
|
28
28
|
from ..cca import load_rate_card
|
|
29
29
|
from ..config import Config
|
|
30
30
|
from ..errors import ConfigError, DataError
|
|
31
|
-
from ..models import ImportPrice, Season, Supplier, TouPeriod
|
|
31
|
+
from ..models import ImportPrice, Season, Supplier, TouPeriod, Utility
|
|
32
32
|
|
|
33
33
|
TARIFF_DATA_ROOT = "tariff"
|
|
34
34
|
|
|
@@ -50,11 +50,11 @@ class TariffSnapshot:
|
|
|
50
50
|
|
|
51
51
|
|
|
52
52
|
@lru_cache(maxsize=8)
|
|
53
|
-
def _snapshots(utility:
|
|
53
|
+
def _snapshots(utility: Utility, tariff: str) -> tuple[TariffSnapshot, ...]:
|
|
54
54
|
directory = (
|
|
55
55
|
files("tariffkit.data")
|
|
56
56
|
/ TARIFF_DATA_ROOT
|
|
57
|
-
/ utility.
|
|
57
|
+
/ utility.data_slug
|
|
58
58
|
/ tariff.lower().replace("-", "")
|
|
59
59
|
)
|
|
60
60
|
if not directory.is_dir():
|
|
@@ -64,19 +64,24 @@ def _snapshots(utility: str, tariff: str) -> tuple[TariffSnapshot, ...]:
|
|
|
64
64
|
if not entry.name.endswith(".toml"):
|
|
65
65
|
continue
|
|
66
66
|
raw = tomllib.loads(entry.read_text(encoding="utf-8"))
|
|
67
|
+
try:
|
|
68
|
+
raw["utility"] = Utility(raw["utility"]).value
|
|
69
|
+
except (KeyError, ValueError) as exc:
|
|
70
|
+
raise DataError(f"{entry.name}: invalid utility identifier") from exc
|
|
67
71
|
found.append(TariffSnapshot(date.fromisoformat(raw["effective"]), raw))
|
|
68
72
|
if not found:
|
|
69
73
|
raise DataError(f"no tariff snapshots for {utility}/{tariff}")
|
|
70
74
|
return tuple(sorted(found, key=lambda s: s.effective))
|
|
71
75
|
|
|
72
76
|
|
|
73
|
-
def load_snapshot(utility: str, tariff: str, on: date) -> TariffSnapshot:
|
|
77
|
+
def load_snapshot(utility: Utility | str, tariff: str, on: date) -> TariffSnapshot:
|
|
74
78
|
"""The snapshot in force on ``on`` -- the latest one not in the future."""
|
|
75
|
-
|
|
79
|
+
utility_id = Utility(utility)
|
|
80
|
+
snapshots = _snapshots(utility_id, tariff)
|
|
76
81
|
applicable = [s for s in snapshots if s.effective <= on]
|
|
77
82
|
if not applicable:
|
|
78
83
|
raise DataError(
|
|
79
|
-
f"{
|
|
84
|
+
f"{utility_id.value}/{tariff}: no snapshot effective on or before {on}; "
|
|
80
85
|
f"earliest vendored is {snapshots[0].effective}"
|
|
81
86
|
)
|
|
82
87
|
return applicable[-1]
|
|
@@ -6,7 +6,7 @@ from datetime import date, datetime, timedelta
|
|
|
6
6
|
|
|
7
7
|
import pytest
|
|
8
8
|
|
|
9
|
-
from tariffkit import Config, Season, Supplier, TouPeriod
|
|
9
|
+
from tariffkit import Config, Season, Supplier, TouPeriod, Utility
|
|
10
10
|
from tariffkit.config import CcaConfig
|
|
11
11
|
from tariffkit.errors import ConfigError
|
|
12
12
|
from tariffkit.tariff.retail import RetailTariff, load_snapshot
|
|
@@ -38,7 +38,7 @@ def test_components_sum_to_published_total(season: str, period: str, published:
|
|
|
38
38
|
|
|
39
39
|
This is the check that catches a mistyped digit in the vendored tariff.
|
|
40
40
|
"""
|
|
41
|
-
snapshot = load_snapshot(
|
|
41
|
+
snapshot = load_snapshot(Utility.PACIFIC_GAS_AND_ELECTRIC, "E-ELEC", date(2026, 6, 1))
|
|
42
42
|
energy = snapshot.raw["energy"][season][period]
|
|
43
43
|
total = sum(energy.values()) + sum(snapshot.raw["adders"].values())
|
|
44
44
|
assert total == pytest.approx(published, abs=5e-6)
|
|
@@ -125,10 +125,14 @@ def test_effective_dated_snapshot_selection() -> None:
|
|
|
125
125
|
(Advice 7846-E, effective 2026-03-01), not from the later reissue of the
|
|
126
126
|
totals page; see tools/regen_tariff.py::pick_effective.
|
|
127
127
|
"""
|
|
128
|
-
assert load_snapshot(
|
|
129
|
-
|
|
128
|
+
assert load_snapshot(
|
|
129
|
+
Utility.PACIFIC_GAS_AND_ELECTRIC, "E-ELEC", date(2026, 7, 1)
|
|
130
|
+
).effective == date(2026, 3, 1)
|
|
131
|
+
assert load_snapshot(
|
|
132
|
+
Utility.PACIFIC_GAS_AND_ELECTRIC, "E-ELEC", date(2026, 4, 15)
|
|
133
|
+
).effective == date(2026, 3, 1)
|
|
130
134
|
with pytest.raises(Exception, match="no snapshot effective"):
|
|
131
|
-
load_snapshot(
|
|
135
|
+
load_snapshot(Utility.PACIFIC_GAS_AND_ELECTRIC, "E-ELEC", date(2020, 1, 1))
|
|
132
136
|
|
|
133
137
|
|
|
134
138
|
def test_base_services_charge_is_daily_and_excluded_from_energy_price(
|
|
@@ -212,7 +216,7 @@ class TestCca:
|
|
|
212
216
|
pcia_vintage is documented as resolving both; this is what makes that
|
|
213
217
|
true rather than true-for-now.
|
|
214
218
|
"""
|
|
215
|
-
cca = load_snapshot(
|
|
219
|
+
cca = load_snapshot(Utility.PACIFIC_GAS_AND_ELECTRIC, "E-ELEC", date(2026, 6, 1)).raw["cca"]
|
|
216
220
|
assert set(cca["pcia_vintages"]) == set(cca["franchise_fee_vintages"])
|
|
217
221
|
|
|
218
222
|
@pytest.mark.parametrize(
|
|
@@ -6,9 +6,9 @@ from datetime import UTC, date, datetime, timedelta
|
|
|
6
6
|
|
|
7
7
|
import pytest
|
|
8
8
|
|
|
9
|
-
from tariffkit import Config, RateEngine, Supplier
|
|
9
|
+
from tariffkit import Config, RateEngine, Supplier, Utility
|
|
10
10
|
from tariffkit.config import CcaConfig
|
|
11
|
-
from tariffkit.errors import ConfigError, OutOfRangeError
|
|
11
|
+
from tariffkit.errors import ConfigError, DataError, OutOfRangeError
|
|
12
12
|
from tariffkit.timeutil import (
|
|
13
13
|
PACIFIC,
|
|
14
14
|
DayType,
|
|
@@ -278,6 +278,29 @@ class TestConfigValidation:
|
|
|
278
278
|
assert config.cca is not None
|
|
279
279
|
assert config.cca.name == "MCE"
|
|
280
280
|
|
|
281
|
+
def test_canonical_utility_identifier_is_emitted(self) -> None:
|
|
282
|
+
config = Config()
|
|
283
|
+
|
|
284
|
+
assert config.utility is Utility.PACIFIC_GAS_AND_ELECTRIC
|
|
285
|
+
assert config.to_dict()["utility"] == Utility.PACIFIC_GAS_AND_ELECTRIC.value
|
|
286
|
+
assert RateEngine(config).describe()["utility"] == Utility.PACIFIC_GAS_AND_ELECTRIC.value
|
|
287
|
+
assert (
|
|
288
|
+
RateEngine(config).tariff.snapshot_for(pt(2026, 8, 16, 9)).raw["utility"]
|
|
289
|
+
== Utility.PACIFIC_GAS_AND_ELECTRIC.value
|
|
290
|
+
)
|
|
291
|
+
|
|
292
|
+
def test_uppercase_pge_is_not_a_utility_identifier(self) -> None:
|
|
293
|
+
with pytest.raises(ConfigError, match="is not a valid Utility"):
|
|
294
|
+
Config.from_dict({"utility": "PGE"})
|
|
295
|
+
|
|
296
|
+
def test_lowercase_pge_identifies_portland_without_loading_california_rates(self) -> None:
|
|
297
|
+
config = Config.from_dict({"utility": "pge"})
|
|
298
|
+
|
|
299
|
+
assert config.utility is Utility.PORTLAND_GENERAL_ELECTRIC
|
|
300
|
+
assert config.to_dict()["utility"] == "pge"
|
|
301
|
+
with pytest.raises(DataError, match="Portland General Electric pricing is not supported"):
|
|
302
|
+
RateEngine(config)
|
|
303
|
+
|
|
281
304
|
def test_from_dict_rejects_unknown_keys(self) -> None:
|
|
282
305
|
with pytest.raises(ConfigError, match="unknown config keys"):
|
|
283
306
|
Config.from_dict({"supplier": "bundled", "nonsense": 1})
|
|
@@ -306,6 +329,7 @@ def test_describe_reports_provenance(engine: RateEngine) -> None:
|
|
|
306
329
|
assert info["export_vintage"] == "NBT26"
|
|
307
330
|
assert info["tariff_effective"] == "2026-03-01"
|
|
308
331
|
assert info["tariff_advice_letter"] == "7846-E"
|
|
332
|
+
assert info["pto_date"] == "2026-06-03"
|
|
309
333
|
assert info["lock_end"] == "2035-06-02"
|
|
310
334
|
|
|
311
335
|
|
|
@@ -89,6 +89,10 @@ class TestDiscovery:
|
|
|
89
89
|
self, payloads: list[tuple[str, dict[str, Any]]]
|
|
90
90
|
) -> None:
|
|
91
91
|
assert {p["device"]["identifiers"][0] for _, p in payloads} == {"tariffkit"}
|
|
92
|
+
assert {p["device"]["manufacturer"] for _, p in payloads} == {
|
|
93
|
+
"Pacific Gas and Electric Company"
|
|
94
|
+
}
|
|
95
|
+
assert {p["device"]["name"] for _, p in payloads} == {"PG&E Rates"}
|
|
92
96
|
assert all(p["availability_topic"] == "tariffkit/status" for _, p in payloads)
|
|
93
97
|
|
|
94
98
|
def test_unique_ids_are_distinct(self, payloads: list[tuple[str, dict[str, Any]]]) -> None:
|
|
@@ -10,7 +10,7 @@ from datetime import date, datetime
|
|
|
10
10
|
|
|
11
11
|
import pytest
|
|
12
12
|
|
|
13
|
-
from tariffkit import Config, RateEngine, Supplier
|
|
13
|
+
from tariffkit import Config, RateEngine, Supplier, Utility
|
|
14
14
|
from tariffkit.cca import load_rate_card
|
|
15
15
|
from tariffkit.config import CcaConfig
|
|
16
16
|
from tariffkit.errors import DataError
|
|
@@ -67,6 +67,10 @@ def test_cost_relief_credit_expires_at_the_end_of_2026() -> None:
|
|
|
67
67
|
)
|
|
68
68
|
|
|
69
69
|
|
|
70
|
+
def test_rate_card_uses_canonical_utility_identifier() -> None:
|
|
71
|
+
assert load_rate_card("mce", CARD_DATE).raw["utility"] == "pacific_gas_and_electric"
|
|
72
|
+
|
|
73
|
+
|
|
70
74
|
@pytest.mark.parametrize("schedule", ["E-ELEC", "E-TOU-C", "EV2-A"])
|
|
71
75
|
def test_mce_generation_is_at_parity_with_pge_today(schedule: str) -> None:
|
|
72
76
|
"""Parity is real but not guaranteed -- fail loudly if MCE diverges.
|
|
@@ -75,7 +79,7 @@ def test_mce_generation_is_at_parity_with_pge_today(schedule: str) -> None:
|
|
|
75
79
|
a bug in the engine. Checked per schedule, since MCE prices each separately.
|
|
76
80
|
"""
|
|
77
81
|
card = load_rate_card("mce", CARD_DATE)
|
|
78
|
-
snapshot = load_snapshot(
|
|
82
|
+
snapshot = load_snapshot(Utility.PACIFIC_GAS_AND_ELECTRIC, schedule, date(2026, 6, 1))
|
|
79
83
|
for season, periods in snapshot.raw["energy"].items():
|
|
80
84
|
for period, components in periods.items():
|
|
81
85
|
assert card.generation(schedule, season, period) == pytest.approx(
|
|
@@ -11,7 +11,7 @@ from datetime import date, datetime, timedelta
|
|
|
11
11
|
|
|
12
12
|
import pytest
|
|
13
13
|
|
|
14
|
-
from tariffkit import Config, RateEngine, Season, TouPeriod
|
|
14
|
+
from tariffkit import Config, RateEngine, Season, TouPeriod, Utility
|
|
15
15
|
from tariffkit.billing.engine import BillEngine
|
|
16
16
|
from tariffkit.billing.models import Bill, BillingPeriod, IntervalReading
|
|
17
17
|
from tariffkit.errors import ConfigError
|
|
@@ -26,7 +26,7 @@ def cells() -> list[tuple[str, str, str]]:
|
|
|
26
26
|
"""(tariff, season, period) for every rate the sheets publish."""
|
|
27
27
|
found = []
|
|
28
28
|
for tariff in SCHEDULES:
|
|
29
|
-
totals = load_snapshot(
|
|
29
|
+
totals = load_snapshot(Utility.PACIFIC_GAS_AND_ELECTRIC, tariff, EFFECTIVE).raw["totals"]
|
|
30
30
|
for season, periods in totals.items():
|
|
31
31
|
found.extend((tariff, season, period) for period in periods)
|
|
32
32
|
return found
|
|
@@ -40,7 +40,7 @@ def test_components_sum_to_published_total(tariff: str, season: str, period: str
|
|
|
40
40
|
On E-TOU-C the total is the over-baseline one, because [adders] carries the
|
|
41
41
|
over-baseline Conservation Incentive Adjustment.
|
|
42
42
|
"""
|
|
43
|
-
raw = load_snapshot(
|
|
43
|
+
raw = load_snapshot(Utility.PACIFIC_GAS_AND_ELECTRIC, tariff, EFFECTIVE).raw
|
|
44
44
|
total = sum(raw["energy"][season][period].values()) + sum(raw["adders"].values())
|
|
45
45
|
assert total == pytest.approx(raw["totals"][season][period], abs=5e-6)
|
|
46
46
|
|
|
@@ -49,7 +49,7 @@ def test_components_sum_to_published_total(tariff: str, season: str, period: str
|
|
|
49
49
|
def test_every_published_cell_is_reachable_by_pricing(tariff: str) -> None:
|
|
50
50
|
"""Guards against a rate table nothing can select, e.g. a stray period."""
|
|
51
51
|
engine = RateEngine(Config(tariff=tariff))
|
|
52
|
-
totals = load_snapshot(
|
|
52
|
+
totals = load_snapshot(Utility.PACIFIC_GAS_AND_ELECTRIC, tariff, EFFECTIVE).raw["totals"]
|
|
53
53
|
priced = {
|
|
54
54
|
(str(p.import_price.season), str(p.import_price.period))
|
|
55
55
|
for month in (7, 12)
|
|
@@ -111,9 +111,14 @@ class TestSharedRiders:
|
|
|
111
111
|
|
|
112
112
|
@pytest.mark.parametrize("table", ["pcia_vintages", "franchise_fee_vintages"])
|
|
113
113
|
def test_all_schedules_agree(self, table: str) -> None:
|
|
114
|
-
reference = load_snapshot(
|
|
114
|
+
reference = load_snapshot(Utility.PACIFIC_GAS_AND_ELECTRIC, "E-ELEC", EFFECTIVE).raw["cca"][
|
|
115
|
+
table
|
|
116
|
+
]
|
|
115
117
|
for tariff in SCHEDULES:
|
|
116
|
-
assert
|
|
118
|
+
assert (
|
|
119
|
+
load_snapshot(Utility.PACIFIC_GAS_AND_ELECTRIC, tariff, EFFECTIVE).raw["cca"][table]
|
|
120
|
+
== reference
|
|
121
|
+
), tariff
|
|
117
122
|
|
|
118
123
|
@pytest.mark.parametrize("tariff", SCHEDULES)
|
|
119
124
|
def test_base_services_charge_is_the_same_ab205_charge(self, tariff: str) -> None:
|
|
@@ -149,7 +154,7 @@ class TestBaseline:
|
|
|
149
154
|
|
|
150
155
|
def test_credit_is_the_spread_between_the_two_cia_rates(self) -> None:
|
|
151
156
|
"""The bill prints one credit; the sheet implements it as two rates."""
|
|
152
|
-
raw = load_snapshot(
|
|
157
|
+
raw = load_snapshot(Utility.PACIFIC_GAS_AND_ELECTRIC, "E-TOU-C", EFFECTIVE).raw["baseline"]
|
|
153
158
|
assert raw["over_rate"] - raw["within_rate"] == pytest.approx(raw["credit"])
|
|
154
159
|
price = RateEngine(Config(tariff="E-TOU-C")).price_at(self.midday(12)).import_price
|
|
155
160
|
assert price.baseline_credit == pytest.approx(0.08140)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{tariffkit-0.2.2 → tariffkit-0.2.3}/src/tariffkit/data/tax/ca_energy_resources/2025-01-01.toml
RENAMED
|
File without changes
|
{tariffkit-0.2.2 → tariffkit-0.2.3}/src/tariffkit/data/tax/ca_energy_resources/2026-01-01.toml
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|