tariffkit 0.2.3__tar.gz → 0.4.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (122) hide show
  1. tariffkit-0.4.0/CHANGELOG.md +486 -0
  2. {tariffkit-0.2.3 → tariffkit-0.4.0}/PKG-INFO +29 -25
  3. {tariffkit-0.2.3 → tariffkit-0.4.0}/README.md +28 -24
  4. {tariffkit-0.2.3 → tariffkit-0.4.0}/pyproject.toml +1 -1
  5. {tariffkit-0.2.3 → tariffkit-0.4.0}/src/tariffkit/__init__.py +2 -0
  6. {tariffkit-0.2.3 → tariffkit-0.4.0}/src/tariffkit/account/cli.py +15 -0
  7. {tariffkit-0.2.3 → tariffkit-0.4.0}/src/tariffkit/account/model.py +92 -24
  8. {tariffkit-0.2.3 → tariffkit-0.4.0}/src/tariffkit/billing/__init__.py +8 -0
  9. {tariffkit-0.2.3 → tariffkit-0.4.0}/src/tariffkit/billing/engine.py +26 -0
  10. {tariffkit-0.2.3 → tariffkit-0.4.0}/src/tariffkit/billing/ledger.py +63 -1
  11. {tariffkit-0.2.3 → tariffkit-0.4.0}/src/tariffkit/billing/models.py +85 -7
  12. {tariffkit-0.2.3 → tariffkit-0.4.0}/src/tariffkit/billing/netting.py +61 -5
  13. {tariffkit-0.2.3 → tariffkit-0.4.0}/src/tariffkit/billing/trueup.py +140 -14
  14. {tariffkit-0.2.3 → tariffkit-0.4.0}/src/tariffkit/cca.py +5 -0
  15. tariffkit-0.4.0/src/tariffkit/components.py +200 -0
  16. {tariffkit-0.2.3 → tariffkit-0.4.0}/src/tariffkit/config.py +62 -0
  17. tariffkit-0.4.0/src/tariffkit/data/tariff/pge/e1/2026-03-01.toml +224 -0
  18. {tariffkit-0.2.3 → tariffkit-0.4.0}/src/tariffkit/data/tariff/pge/etouc/2025-01-01.toml +5 -1
  19. {tariffkit-0.2.3 → tariffkit-0.4.0}/src/tariffkit/data/tariff/pge/etouc/2025-03-01.toml +5 -1
  20. {tariffkit-0.2.3 → tariffkit-0.4.0}/src/tariffkit/data/tariff/pge/etouc/2025-09-01.toml +5 -1
  21. {tariffkit-0.2.3 → tariffkit-0.4.0}/src/tariffkit/data/tariff/pge/etouc/2026-01-01.toml +5 -1
  22. {tariffkit-0.2.3 → tariffkit-0.4.0}/src/tariffkit/data/tariff/pge/etouc/2026-03-01.toml +5 -1
  23. tariffkit-0.4.0/src/tariffkit/data/tariff/pge/etoud/2026-03-01.toml +143 -0
  24. tariffkit-0.4.0/src/tariffkit/data/tariff/pge/program/dcare/2025-01-01.toml +14 -0
  25. tariffkit-0.4.0/src/tariffkit/data/tariff/pge/program/dmedical/2026-03-01.toml +14 -0
  26. tariffkit-0.4.0/src/tariffkit/data/tariff/pge/program/ersmart/2022-05-01.toml +19 -0
  27. tariffkit-0.4.0/src/tariffkit/data/tariff/pge/program/medicalbaseline/2023-03-23.toml +13 -0
  28. {tariffkit-0.2.3 → tariffkit-0.4.0}/src/tariffkit/engine.py +10 -1
  29. {tariffkit-0.2.3 → tariffkit-0.4.0}/src/tariffkit/interop/predbat.py +17 -11
  30. {tariffkit-0.2.3 → tariffkit-0.4.0}/src/tariffkit/models.py +20 -0
  31. {tariffkit-0.2.3 → tariffkit-0.4.0}/src/tariffkit/mqtt/discovery.py +53 -1
  32. {tariffkit-0.2.3 → tariffkit-0.4.0}/src/tariffkit/mqtt/publisher.py +40 -0
  33. {tariffkit-0.2.3 → tariffkit-0.4.0}/src/tariffkit/providers/pge/reconcile.py +39 -21
  34. {tariffkit-0.2.3 → tariffkit-0.4.0}/src/tariffkit/providers/pge/statements/parse.py +3 -0
  35. {tariffkit-0.2.3 → tariffkit-0.4.0}/src/tariffkit/tariff/retail.py +84 -31
  36. tariffkit-0.4.0/tests/conftest.py +61 -0
  37. {tariffkit-0.2.3 → tariffkit-0.4.0}/tests/test_account.py +142 -2
  38. {tariffkit-0.2.3 → tariffkit-0.4.0}/tests/test_account_cli.py +20 -1
  39. {tariffkit-0.2.3 → tariffkit-0.4.0}/tests/test_billing.py +144 -0
  40. tariffkit-0.4.0/tests/test_components.py +164 -0
  41. {tariffkit-0.2.3 → tariffkit-0.4.0}/tests/test_eelec.py +9 -4
  42. {tariffkit-0.2.3 → tariffkit-0.4.0}/tests/test_engine.py +48 -0
  43. tariffkit-0.4.0/tests/test_ha_backfill.py +890 -0
  44. tariffkit-0.4.0/tests/test_ha_bank.py +411 -0
  45. tariffkit-0.4.0/tests/test_ha_energy.py +965 -0
  46. {tariffkit-0.2.3 → tariffkit-0.4.0}/tests/test_integrations.py +25 -2
  47. {tariffkit-0.2.3 → tariffkit-0.4.0}/tests/test_interop.py +39 -14
  48. {tariffkit-0.2.3 → tariffkit-0.4.0}/tests/test_ledger.py +63 -0
  49. {tariffkit-0.2.3 → tariffkit-0.4.0}/tests/test_mqtt_publisher.py +54 -5
  50. {tariffkit-0.2.3 → tariffkit-0.4.0}/tests/test_schedules.py +155 -16
  51. {tariffkit-0.2.3 → tariffkit-0.4.0}/tests/test_trueup.py +142 -1
  52. tariffkit-0.2.3/CHANGELOG.md +0 -247
  53. tariffkit-0.2.3/tests/conftest.py +0 -26
  54. {tariffkit-0.2.3 → tariffkit-0.4.0}/.gitignore +0 -0
  55. {tariffkit-0.2.3 → tariffkit-0.4.0}/LICENSE +0 -0
  56. {tariffkit-0.2.3 → tariffkit-0.4.0}/src/tariffkit/account/__init__.py +0 -0
  57. {tariffkit-0.2.3 → tariffkit-0.4.0}/src/tariffkit/account/errors.py +0 -0
  58. {tariffkit-0.2.3 → tariffkit-0.4.0}/src/tariffkit/account/rates.py +0 -0
  59. {tariffkit-0.2.3 → tariffkit-0.4.0}/src/tariffkit/account/repository.py +0 -0
  60. {tariffkit-0.2.3 → tariffkit-0.4.0}/src/tariffkit/cli.py +0 -0
  61. {tariffkit-0.2.3 → tariffkit-0.4.0}/src/tariffkit/data/__init__.py +0 -0
  62. {tariffkit-0.2.3 → tariffkit-0.4.0}/src/tariffkit/data/cca/mce/2023-01-01.toml +0 -0
  63. {tariffkit-0.2.3 → tariffkit-0.4.0}/src/tariffkit/data/cca/mce/2026-04-01.toml +0 -0
  64. {tariffkit-0.2.3 → tariffkit-0.4.0}/src/tariffkit/data/export/pge/acc_plus/2023-04-15.toml +0 -0
  65. {tariffkit-0.2.3 → tariffkit-0.4.0}/src/tariffkit/data/export/pge/nbt00.json.gz +0 -0
  66. {tariffkit-0.2.3 → tariffkit-0.4.0}/src/tariffkit/data/export/pge/nbt23.json.gz +0 -0
  67. {tariffkit-0.2.3 → tariffkit-0.4.0}/src/tariffkit/data/export/pge/nbt24.json.gz +0 -0
  68. {tariffkit-0.2.3 → tariffkit-0.4.0}/src/tariffkit/data/export/pge/nbt25.json.gz +0 -0
  69. {tariffkit-0.2.3 → tariffkit-0.4.0}/src/tariffkit/data/export/pge/nbt26.json.gz +0 -0
  70. {tariffkit-0.2.3 → tariffkit-0.4.0}/src/tariffkit/data/holidays.toml +0 -0
  71. {tariffkit-0.2.3 → tariffkit-0.4.0}/src/tariffkit/data/manifest.json +0 -0
  72. {tariffkit-0.2.3 → tariffkit-0.4.0}/src/tariffkit/data/nsc/pge.toml +0 -0
  73. {tariffkit-0.2.3 → tariffkit-0.4.0}/src/tariffkit/data/tariff/pge/eelec/2025-01-01.toml +0 -0
  74. {tariffkit-0.2.3 → tariffkit-0.4.0}/src/tariffkit/data/tariff/pge/eelec/2025-03-01.toml +0 -0
  75. {tariffkit-0.2.3 → tariffkit-0.4.0}/src/tariffkit/data/tariff/pge/eelec/2025-09-01.toml +0 -0
  76. {tariffkit-0.2.3 → tariffkit-0.4.0}/src/tariffkit/data/tariff/pge/eelec/2026-01-01.toml +0 -0
  77. {tariffkit-0.2.3 → tariffkit-0.4.0}/src/tariffkit/data/tariff/pge/eelec/2026-03-01.toml +0 -0
  78. {tariffkit-0.2.3 → tariffkit-0.4.0}/src/tariffkit/data/tariff/pge/ev2a/2025-01-01.toml +0 -0
  79. {tariffkit-0.2.3 → tariffkit-0.4.0}/src/tariffkit/data/tariff/pge/ev2a/2025-03-01.toml +0 -0
  80. {tariffkit-0.2.3 → tariffkit-0.4.0}/src/tariffkit/data/tariff/pge/ev2a/2025-09-01.toml +0 -0
  81. {tariffkit-0.2.3 → tariffkit-0.4.0}/src/tariffkit/data/tariff/pge/ev2a/2026-01-01.toml +0 -0
  82. {tariffkit-0.2.3 → tariffkit-0.4.0}/src/tariffkit/data/tariff/pge/ev2a/2026-03-01.toml +0 -0
  83. {tariffkit-0.2.3 → tariffkit-0.4.0}/src/tariffkit/data/tax/ca_energy_resources/2025-01-01.toml +0 -0
  84. {tariffkit-0.2.3 → tariffkit-0.4.0}/src/tariffkit/data/tax/ca_energy_resources/2026-01-01.toml +0 -0
  85. {tariffkit-0.2.3 → tariffkit-0.4.0}/src/tariffkit/data/versioned.py +0 -0
  86. {tariffkit-0.2.3 → tariffkit-0.4.0}/src/tariffkit/errors.py +0 -0
  87. {tariffkit-0.2.3 → tariffkit-0.4.0}/src/tariffkit/export/__init__.py +0 -0
  88. {tariffkit-0.2.3 → tariffkit-0.4.0}/src/tariffkit/export/nbt.py +0 -0
  89. {tariffkit-0.2.3 → tariffkit-0.4.0}/src/tariffkit/interop/__init__.py +0 -0
  90. {tariffkit-0.2.3 → tariffkit-0.4.0}/src/tariffkit/interop/emhass.py +0 -0
  91. {tariffkit-0.2.3 → tariffkit-0.4.0}/src/tariffkit/interop/slots.py +0 -0
  92. {tariffkit-0.2.3 → tariffkit-0.4.0}/src/tariffkit/mqtt/__init__.py +0 -0
  93. {tariffkit-0.2.3 → tariffkit-0.4.0}/src/tariffkit/providers/__init__.py +0 -0
  94. {tariffkit-0.2.3 → tariffkit-0.4.0}/src/tariffkit/providers/pge/__init__.py +0 -0
  95. {tariffkit-0.2.3 → tariffkit-0.4.0}/src/tariffkit/providers/pge/statements/__init__.py +0 -0
  96. {tariffkit-0.2.3 → tariffkit-0.4.0}/src/tariffkit/providers/pge/statements/errors.py +0 -0
  97. {tariffkit-0.2.3 → tariffkit-0.4.0}/src/tariffkit/providers/pge/statements/model.py +0 -0
  98. {tariffkit-0.2.3 → tariffkit-0.4.0}/src/tariffkit/providers/pge/statements/ocr.py +0 -0
  99. {tariffkit-0.2.3 → tariffkit-0.4.0}/src/tariffkit/py.typed +0 -0
  100. {tariffkit-0.2.3 → tariffkit-0.4.0}/src/tariffkit/secrets.py +0 -0
  101. {tariffkit-0.2.3 → tariffkit-0.4.0}/src/tariffkit/sources/__init__.py +0 -0
  102. {tariffkit-0.2.3 → tariffkit-0.4.0}/src/tariffkit/sources/greenbutton.py +0 -0
  103. {tariffkit-0.2.3 → tariffkit-0.4.0}/src/tariffkit/sources/homeassistant.py +0 -0
  104. {tariffkit-0.2.3 → tariffkit-0.4.0}/src/tariffkit/sources/influx.py +0 -0
  105. {tariffkit-0.2.3 → tariffkit-0.4.0}/src/tariffkit/sources/pge.py +0 -0
  106. {tariffkit-0.2.3 → tariffkit-0.4.0}/src/tariffkit/tariff/__init__.py +0 -0
  107. {tariffkit-0.2.3 → tariffkit-0.4.0}/src/tariffkit/timeutil.py +0 -0
  108. {tariffkit-0.2.3 → tariffkit-0.4.0}/src/tariffkit/web/__init__.py +0 -0
  109. {tariffkit-0.2.3 → tariffkit-0.4.0}/src/tariffkit/web/app.py +0 -0
  110. {tariffkit-0.2.3 → tariffkit-0.4.0}/tests/fixtures/nbt_golden_rows.jsonl +0 -0
  111. {tariffkit-0.2.3 → tariffkit-0.4.0}/tests/fixtures/statements/synthetic_cca_ratechange.txt +0 -0
  112. {tariffkit-0.2.3 → tariffkit-0.4.0}/tests/test_ev2a.py +0 -0
  113. {tariffkit-0.2.3 → tariffkit-0.4.0}/tests/test_export_golden.py +0 -0
  114. {tariffkit-0.2.3 → tariffkit-0.4.0}/tests/test_mce.py +0 -0
  115. {tariffkit-0.2.3 → tariffkit-0.4.0}/tests/test_pge_reconcile.py +0 -0
  116. {tariffkit-0.2.3 → tariffkit-0.4.0}/tests/test_pge_statements.py +0 -0
  117. {tariffkit-0.2.3 → tariffkit-0.4.0}/tests/test_private_config.py +0 -0
  118. {tariffkit-0.2.3 → tariffkit-0.4.0}/tests/test_sources_greenbutton.py +0 -0
  119. {tariffkit-0.2.3 → tariffkit-0.4.0}/tests/test_sources_ha.py +0 -0
  120. {tariffkit-0.2.3 → tariffkit-0.4.0}/tests/test_sources_influx.py +0 -0
  121. {tariffkit-0.2.3 → tariffkit-0.4.0}/tests/test_sources_pge.py +0 -0
  122. {tariffkit-0.2.3 → tariffkit-0.4.0}/tests/test_versioned.py +0 -0
@@ -0,0 +1,486 @@
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.4.0] - 2026-08-26
9
+
10
+ ### Added
11
+ - `tariffkit.billing.run_lifetime` folds a run of bills from end to end,
12
+ applying each annual settlement in the order it falls and returning a
13
+ `LifetimeLedger` that reports what every cycle opened with. `run_ledger`
14
+ carries a bank between cycles but knows nothing about the year closing on it,
15
+ and `run_true_ups` computes each event independently of the others -- so
16
+ composing them naively lets a second settlement be derived from a ledger that
17
+ never saw the first one's clawback. This sequencing had grown inside the Home
18
+ Assistant integration, where it was neither tested against tariff text nor
19
+ visible to anyone reading the library.
20
+
21
+ - `Bill.import_charges` (energy charges plus the statutory taxes beside them),
22
+ `Bill.marginal_buckets` (a span's time-of-use split, from two cycle-to-date
23
+ bills), `CreditBalances.held_by` (which buckets each settling party holds),
24
+ `AccountProfile.pto_date` (the earliest Permission To Operate any epoch
25
+ records), and `TrueUp.settles`. Each replaces a copy that had accumulated in
26
+ the Home Assistant integration; `held_by`'s copy had come to disagree with
27
+ `tariffkit.billing.trueup` about who owns the generation bucket.
28
+
29
+ - `check_coverage` accepts `netted=True` for readings that come from a meter's
30
+ own import and export registers, and `through=<moment>` for a period still
31
+ running. Callers who knew these facts were filtering the function's messages
32
+ by their text, which stops filtering the moment a new warning is added -- as
33
+ one was.
34
+
35
+ `through` also closes a hole that predates the flag. A missing hour at the
36
+ *end* of a series is not a gap between readings -- a gap needs a reading on
37
+ each side, and the whole point of a series that has stopped is that there is
38
+ nothing on the far side -- so only a clock can tell an hour that arrived
39
+ empty from one that has not arrived. Without one, a meter that stopped
40
+ reporting went on producing a smaller figure that still called itself
41
+ complete, indefinitely. `check_coverage` now measures the shortfall against
42
+ elapsed time and names a series that has stopped, and the Home Assistant
43
+ entities pass their clock so the running totals get both.
44
+
45
+ - The Home Assistant integration can optionally track what the meter actually
46
+ moved. Name the cumulative grid-import and grid-export kWh entities under
47
+ **Configure → Metered energy** — deliberately not part of initial setup,
48
+ since pricing needs no meter and the counters are usually integrated after
49
+ the tariff — and it adds running Energy Cost, Export Credit, and Amount Due
50
+ entities for today and for the billing cycle to date, alongside Grid Import
51
+ and Grid Export totals.
52
+
53
+ Amount Due is what a statement would charge, not what a bill sums to. Under
54
+ Net Billing a cycle earning more credit than it owes does not produce a
55
+ refund: the excess banks, and a credit may only offset charges the tariff
56
+ lets it reach, so Non-Bypassable Charges stay due however large the bank. The
57
+ figure comes from `tariffkit.billing.apply_credits`; `credit_applied` and
58
+ `bank_change` attributes say where the difference went. Reading `Bill.total`
59
+ instead went negative on a heavily exporting cycle, which no statement does.
60
+
61
+ Where the bank cannot be trusted -- not folded yet, unreadable, or folded
62
+ across a gap -- the figures are stated before any bank offsets them, the
63
+ reason appears in `warnings`, and `quality.complete` is false. Silently using
64
+ a doubtful balance halved a cycle's charge while reporting itself complete.
65
+
66
+ The counters do not have to reset daily: each hour's energy comes from the
67
+ recorder's own long-term statistics, which already absorb counter restarts
68
+ and reload gaps, with the hour in progress read live off entity state.
69
+ Readings are priced by `tariffkit.billing.BillEngine`, the same code that
70
+ reconciles a printed statement, so the running figures carry time-of-use
71
+ bucketing, the Energy Commission Tax, the baseline credit, the whole day's
72
+ Base Services Charge, and the rule that exports before Permission To Operate
73
+ earn nothing.
74
+
75
+ Today's figures are the cycle's movement across today rather than a one-day
76
+ bill, because parts of a bill are cumulative over a cycle rather than
77
+ additive over its days -- the baseline allowance is granted per cycle and
78
+ consumed in day order, so pricing a day alone grants it one day's allowance
79
+ however much the cycle had banked. Gaps, overlaps and reconstructed intervals
80
+ in the metered series are reported in `warnings` and clear
81
+ `quality.complete`, so a recorder outage understates the figure loudly rather
82
+ than quietly.
83
+
84
+ A **Billing cycle start day** setting names the day of the month the meter is
85
+ read, but it is only a fallback: where the profile carries imported
86
+ statements the cycle boundary comes from the statements themselves, which is
87
+ the only way to match a real bill -- PG&E reads on business days, so
88
+ consecutive cycles open on the 29th, the 30th, the 1st and the 3rd. A
89
+ `cycle_boundary` attribute reports which was used.
90
+
91
+ Naming no entities creates none of these, leaving every existing entity
92
+ byte-identical. One exception is worth knowing: an account profile imported
93
+ from the CLI carries its own `meter_sources.ha` mapping, and that mapping is
94
+ honoured, so such an entry gains the entities without anyone opening the
95
+ form.
96
+
97
+ - A `tariffkit.backfill_usage` action prices metered history into long-term
98
+ statistics, so cost and credit for days before the meters were configured
99
+ appear in Home Assistant rather than only through the CLI. It writes external
100
+ statistics under a `tariffkit:` namespace -- the shape `opower` uses for
101
+ utility history -- which leaves the running entities' own series and the
102
+ recorder's compilation of them untouched. One row per finished day, each being
103
+ its cycle's movement across that day, so the days sum to what their cycle
104
+ actually charged. The run is folded through every annual settlement it
105
+ crosses, not merely carried forward, because a true-up claws back credit
106
+ already paid out as Net Surplus Compensation -- so a cycle after an
107
+ anniversary opens with less bank than a straight fold would give it, which is
108
+ what the live entities have always done.
109
+ It defaults to the billing cycle containing the PTO date -- where bills begin
110
+ meaning anything, since Net Billing compensation runs from Permission To
111
+ Operate -- and reports a per-cycle bill alongside the daily rows, which is
112
+ what an export credit ledger folds.
113
+ Rerunning replaces the window rather than appending to it, which is how a
114
+ corrected account history is picked up; a rerun over a later window continues
115
+ the running total it finds rather than restarting it. Only days the recorder
116
+ actually holds readings for are priced, and coverage is judged per meter, so
117
+ one direction cannot vouch for the other.
118
+
119
+ - **Export credit bank** entities carry the Net Billing credit balance
120
+ between cycles, which no entity computing forward from the day meters were
121
+ configured can know. It folds every closed cycle since the one containing the
122
+ PTO date -- so it opens at zero by construction, with no balance anyone has to
123
+ supply -- applying each cycle's credits against its charges through
124
+ `tariffkit.billing.run_ledger`, and continuing from a true-up's own closing
125
+ balance where the run crosses one. It refuses rather than reports where the
126
+ run has a gap: the library's ledger deliberately does not check for one, and a
127
+ bank folded across a missing cycle reports a balance that never existed. The
128
+ balance is recomputed when a cycle closes rather than accumulated, so
129
+ correcting account history fixes it instead of leaving a stored figure quietly
130
+ wrong. There are two entities because a Community Choice Aggregator account
131
+ has two banks -- the utility's delivery and bonus credits against the CCA's
132
+ export credit -- settling on unrelated calendars, which a single total would
133
+ merge into a figure no statement prints. Every annual settlement in a run is
134
+ applied in order rather than only the most recent, since each is computed
135
+ independently and a later one cannot see an earlier one's clawback. A
136
+ settlement that reverses and pays nothing -- the utility's, on a CCA account
137
+ -- is recorded without consuming cycles, so it cannot shorten the other
138
+ supplier's cash-out year and leave unreversed credit in the bank.
139
+
140
+ ### Changed
141
+ - **Only affects development checkouts.** The metered-energy entities below are
142
+ new in this release, so no published version ever carried their earlier
143
+ names; this note is for anyone who ran them from `main` before the rename.
144
+ See [Upgrading](docs/home-assistant.md#upgrading). The
145
+ Net Cost entities are renamed **Amount Due** (`net_cost_today` /
146
+ `net_cost_cycle` become `amount_due_today` / `amount_due_cycle`), and the
147
+ backfill's `tariffkit:<profile>_net_cost` statistic becomes
148
+ `tariffkit:<profile>_amount_due`.
149
+
150
+ The rename is the point, not a side effect. The figure changed meaning and
151
+ sign -- it was charges less every credit earned, which goes negative; it is
152
+ now what a statement charges, which does not. Home Assistant's statistics
153
+ compiler accumulates a `total` sensor as `sum += new - old` whenever
154
+ `last_reset` is unchanged, and the cycle entity's `last_reset` is the cycle
155
+ start, so keeping the old id would have added the whole banked balance to the
156
+ lifetime sum in a single compile and never washed it out. A new unique id
157
+ abandons the old series intact rather than corrupting it.
158
+
159
+ Update any dashboard card, template, or automation that names the old
160
+ entities, and re-run the backfill: history published by the older code was
161
+ priced as `Bill.total` and without the annual settlements. The old
162
+ `net_cost_*` entities are removed from the registry on reload; their recorded
163
+ statistics remain and can be deleted under **Developer tools -> Statistics**.
164
+
165
+ ### Fixed
166
+ - `run_true_ups` no longer emits a Community Choice Aggregator cash-out for a
167
+ bundled account. There is no aggregator to settle with, and on such an
168
+ account PG&E supplies generation, so the cash-out and the Relevant Period
169
+ each clawed back the same generation credit for the same exported energy.
170
+
171
+ - `apply_credits` no longer reports a negative `cash_due`. `non_offsettable`
172
+ can go negative on its own -- `baseline_credit` is a negative import
173
+ component listed there -- and at a high export-to-import ratio it outweighs
174
+ the charges beside it. Credit that cannot be spent stays in the bank instead.
175
+
176
+ - Statement evidence is identified by what a statement says rather than by the
177
+ bytes it arrived in, so re-importing evidence a profile already holds is a
178
+ no-op. The utility regenerates a bill PDF on every request -- the same
179
+ statement downloaded twice is byte-different and hashes differently -- and
180
+ `AccountObservation.identity()` preferred that digest, so nothing a profile
181
+ held ever matched and every `account sync --apply` appended its whole window
182
+ again. Profiles written before this collapse their repeats the next time they
183
+ are loaded, so no migration is needed. `source_digest` remains as provenance,
184
+ and a profile still refuses to hold two observations that name the same source
185
+ document while disagreeing about what it says -- keyed on the top-level digest
186
+ falling back to the agreements' own, so an observation carrying only the
187
+ latter is still guarded. The extraction mode is excluded from identity
188
+ alongside the digest: the parser falls back to OCR for older statements, and
189
+ how a document was read is not part of what it says.
190
+ - `tariffkit account sync` now signs in before asking the portal for the
191
+ statement list. A resumed session arrives with a live session cookie and no
192
+ CSRF token, because the token is one-shot and deliberately not cached, so the
193
+ first authenticated call failed -- either as a bare "the session token is
194
+ stale" or, when the portal answered with an empty list instead of an error, as
195
+ a silent "received 0 statement update(s)" against an account with 25
196
+ statements. `apex`'s own recovery could not rescue it: it falls back to a
197
+ forced re-login, which fails while already signed in because the login page
198
+ redirects to the community and the token it carries belongs to the wrong
199
+ Lightning app. `audit doctor` was unaffected because it calls `login()` first,
200
+ which is what made the two disagree.
201
+
202
+ ## [0.3.0] - 2026-08-22
203
+
204
+ ### Added
205
+ - PG&E's complete active single-family residential lineup is now covered:
206
+ generated E-1 and E-TOU-D snapshots join E-ELEC, E-TOU-C, and EV2-A.
207
+ E-1 preserves tiered baseline billing while exposing the over-baseline
208
+ marginal price, and E-TOU-D observes its weekday-only 5–8 p.m. peak and
209
+ tariff holiday calendar.
210
+ - Generated, effective-dated D-CARE, D-MEDICAL, Rule 19 Medical Baseline, and
211
+ E-RSMART data now drive residential program adjustments. SmartRate accepts
212
+ explicit announced event dates and marks prices beyond the authoritative
213
+ event horizon incomplete rather than guessing future events.
214
+ - Prices now decompose into a fixed set of chartable component groups —
215
+ generation, distribution, transmission, surcharges, credits, and a catch-all
216
+ other on the import side; generation, delivery, credits, and other on the
217
+ export side. The groups sum back to the price they came from and do not vary
218
+ with the tariff, supplier, or discount, so a chart built against them
219
+ survives an account change. `ImportPrice.grouped()`, `ExportPrice.grouped()`,
220
+ a `groups` key in every `to_dict` payload, and `tariffkit.components` expose
221
+ them to library and REST callers.
222
+ - Home Assistant gains a sensor per component group in each direction, and the
223
+ forecast's `rates` attribute carries the same roll-up per hour, so both the
224
+ recorded past and the next 48 hours can be drawn as stacked charts -- one
225
+ card each, because stacking recorded and forecast points together would
226
+ double-count the current hour. The MQTT publisher publishes the same series
227
+ with matching discovery payloads, each band carrying its price's quality
228
+ flags.
229
+ - Home Assistant now exposes the AB 205 Base Services Charge as **Daily Fixed
230
+ Charge** in `USD/day`. The unit keeps it out of the Energy dashboard's price
231
+ pickers and out of any `USD/kWh` stack, which is why it can be published at
232
+ all: it is a fixed daily amount, not a marginal price.
233
+
234
+ ### Changed
235
+ - Home Assistant now offers every active PG&E residential schedule and
236
+ Medical Baseline configuration while preserving existing profile and entity
237
+ identities.
238
+
239
+ ### Fixed
240
+ - Predbat attributes now use its `from` / `to` / `rate` contract for values
241
+ already expressed in cents. Predbat no longer interprets TariffKit's cents as
242
+ currency units and multiplies them by 100, while Pacific-midnight anchoring
243
+ and complete 46-, 48-, and 50-slot tariff days remain unchanged.
244
+
245
+ ## [0.2.3] - 2026-08-17
246
+
247
+ ### Changed
248
+ - Pacific Gas and Electric now has the unambiguous machine identifier
249
+ `pacific_gas_and_electric`, while Home Assistant and MQTT present `PG&E` or
250
+ the full company name. The separate `pge` identifier correctly means Portland
251
+ General Electric and is recognized but explicitly unsupported for pricing,
252
+ preventing it from ever selecting California tariff data.
253
+ - Home Assistant now labels the export-minus-import calculation explicitly,
254
+ translates time-of-use states for display, and presents the forecast horizon
255
+ as **Rates Available Through**. Forecast metadata and a new **Rate Data
256
+ Status** entity are grouped under diagnostics, where PTO date, export lock
257
+ end, NBT vintage, tariff provenance, source, and quality flags explain the
258
+ active rates without crowding the primary price controls.
259
+
260
+ ### Fixed
261
+ - The PyPI project page now loads the TariffKit banner from an absolute URL
262
+ instead of an unresolved repository-relative path.
263
+
264
+ ## [0.2.2] - 2026-08-16
265
+
266
+ ### Added
267
+ - Project documentation now uses a TariffKit banner built from the integration's
268
+ existing icon and a provider-neutral electricity rate curve.
269
+ - Public contribution and support guidance now provides privacy-safe issue
270
+ forms, private security routing, review ownership, a pull request checklist,
271
+ community conduct expectations, and exact development checks. The guidance
272
+ also makes the generated-data and repository-only audit boundaries explicit
273
+ so public collaboration does not expose utility-account material or alter
274
+ distribution guarantees.
275
+ - Pull requests now run GitHub's dependency review action with read-only
276
+ permissions so vulnerable or disallowed dependency changes fail before merge.
277
+
278
+ ### Changed
279
+ - **Python 3.14.2 and Home Assistant 2026.3.0 are now the supported floors.**
280
+ The lockfile no longer carries the obsolete Home Assistant 2026.2 fallback,
281
+ and CI audits the complete locked dependency graph with a pinned `pip-audit`
282
+ release while retaining raw reports as failure artifacts. The Linux secrets
283
+ extra and Home Assistant tests both inherit the same exact cryptography pin;
284
+ an expiring policy requires each audit to report exactly its three known,
285
+ unreachable advisories and rejects any additional finding.
286
+ - The README now explains that default HACS approval may take months and gives
287
+ complete custom-repository installation steps for use during the review.
288
+ - Workflow actions are pinned to reviewed immutable commits, checkout credentials
289
+ are not persisted, superseded runs are bounded by concurrency controls, and
290
+ repository write access is isolated to the jobs that publish results.
291
+ - MQTT now rejects credentials over plaintext unless the operator explicitly
292
+ allows insecure authentication for an isolated trusted network. Passwords
293
+ also require a username, while anonymous plaintext publishing remains valid.
294
+
295
+ ### Fixed
296
+ - Rate-sheet regeneration now scans trailing table cells with a linear parser,
297
+ avoiding pathological regular-expression backtracking on malformed publisher
298
+ text while preserving accepted dollar, decimal, negative, and change-marker
299
+ forms.
300
+
301
+ ## [0.2.1] - 2026-08-16
302
+
303
+ ### Added
304
+ - **HACS releases now include a deterministic `tariffkit.zip` integration
305
+ artifact.** HACS installs only the tracked component files, rooted directly
306
+ in the integration directory, while the release pipeline validates the ZIP
307
+ against its source, checksums it with the Python distributions, and attaches
308
+ all artifacts before immutable publication. Dedicated HACS and hassfest
309
+ checks also gate integration changes and prepare TariffKit for default-store
310
+ submission.
311
+
312
+ ### Fixed
313
+ - Home Assistant action descriptions now follow hassfest's current service
314
+ schema: icons live in `icons.json`, target metadata is explicit, and the
315
+ config-entry-only YAML schema is declared.
316
+
317
+ ## [0.2.0] - 2026-08-16
318
+
319
+ ### Added
320
+ - **Named, effective-dated account profiles** track tariff, supplier, baseline,
321
+ export, and credential-set changes over a service agreement's lifetime.
322
+ Pricing and billing resolve the settings in force at each timestamp, including
323
+ cycles that cross an account transition. Profile writes are atomic,
324
+ revision-checked, and locked against concurrent updates.
325
+ - **PG&E statement import and portal synchronization** can populate an account
326
+ profile from printed facts. Proposed changes are reported as additions,
327
+ confirmations, conflicts, or missing required values; a statement with a
328
+ conflict cannot be partially applied. Statement support is available through
329
+ the `statements` extra, while the account-specific audit harness remains
330
+ repository-only.
331
+ - **Profile-scoped meter sources** store provider-neutral grid-import and
332
+ grid-export mappings for Home Assistant and InfluxDB 3. The bill command can
333
+ query either source directly, with explicit command options taking precedence
334
+ over profile, environment, and global defaults.
335
+ - **Credential sets backed by the operating-system keyring** hold PG&E, Home
336
+ Assistant, InfluxDB, and MQTT secrets outside configuration files and command
337
+ arguments. Environment injection remains available for containers.
338
+ - **A complete billing layer** now covers interval netting, coverage warnings,
339
+ baseline credits, fixed charges, taxes, export-credit buckets, annual true-up,
340
+ and CCA cash-out. Credits retain their printed statement scope instead of
341
+ being treated as one fungible balance.
342
+ - **Home Assistant and InfluxDB 3 interval sources** complement the Green Button
343
+ reader. Home Assistant reads long-term statistics and prefers five-minute
344
+ data; InfluxDB derives exact totals from cumulative-counter endpoints and
345
+ spreads advances across the time in which they accrued.
346
+ - **E-TOU-C and EV2-A retail schedules**, complete PCIA and franchise-fee
347
+ vintage tables, E-TOU-C baseline allowances, California's electrical-energy
348
+ surcharge, Net Surplus Compensation data, and effective-dated PG&E and MCE
349
+ rate snapshots extend pricing and billing beyond the original E-ELEC
350
+ schedule.
351
+ - **Repository-only rate-data generators** rebuild every vendored dataset from
352
+ its published source and read the rendered result back through runtime code
353
+ before writing it. A weekly workflow checks export matrices, retail tariffs,
354
+ ACC Plus, CCA cards, Net Surplus Compensation, holidays, and the state
355
+ surcharge for upstream changes.
356
+ - **Home Assistant account-history flows** support staged initial setup and
357
+ profile inspection, transition editing, statement import, and sanitized
358
+ profile export. Stable profile-based config-entry identity survives tariff and
359
+ supplier changes.
360
+ - **Home Assistant response actions** provide current or forecast rates in
361
+ native and EMHASS shapes for caller-selected windows. Requests reject
362
+ ambiguous DST-fold timestamps, misaligned windows, and horizons beyond seven
363
+ days rather than guessing.
364
+ - **Home Assistant diagnostics and integration tests** cover config and options
365
+ flows, migration, entities, actions, Energy dashboard compatibility,
366
+ effective-dated provenance, DST handling, and opt-in Predbat output.
367
+ Diagnostics deliberately omit account history, observations, credentials,
368
+ and meter mappings.
369
+ - **Pure interoperability adapters** generate EMHASS, Predbat, and generic slot
370
+ payloads from a `PriceCurve`, shared by Home Assistant and MQTT without either
371
+ integration becoming a dependency.
372
+ - **Request-scoped REST configuration and account selection** allow callers to
373
+ price one request against validated settings or a named profile. Unknown
374
+ configuration keys and credential fields are rejected.
375
+ - **A bind-mounted Home Assistant development stack** runs the custom component
376
+ and local package source in a real container without publishing a wheel.
377
+ - **A build-once release process and maintainer runbook** synchronize Python,
378
+ lockfile, Home Assistant, changelog, and documentation versions. One validated
379
+ wheel/sdist pair moves through optional TestPyPI staging, protected PyPI
380
+ approval, PEP 740 attestations, and an immutable GitHub release.
381
+
382
+ ### Changed
383
+ - **The project is now TariffKit.** The distribution, import package, CLI,
384
+ configuration directory, environment prefix, MQTT namespace, repository
385
+ links, and Home Assistant domain use the utility-neutral `tariffkit` identity.
386
+ The initial data-provider scope remains PG&E and California.
387
+ - **Packaging remains one public distribution in one repository.** Optional
388
+ features use extras and lazy imports, leaving the default runtime
389
+ dependency-free. Rate-data tooling and the account-specific audit harness are
390
+ excluded from wheel and sdist but retain strict lint, typing, and test
391
+ coverage.
392
+ - **Python 3.14 is the minimum**, and Home Assistant 2026.3.0 is therefore the
393
+ integration floor. Development dependencies use PEP 735 groups, package
394
+ licensing uses an SPDX expression, and installed metadata is the runtime
395
+ version source.
396
+ - **Home Assistant installs the exact matching TariffKit release** instead of
397
+ vendoring package source. Its config flow asks only questions made relevant
398
+ by earlier answers and separates account history from forecast and Predbat
399
+ options.
400
+ - **Home Assistant entities are lean and recorder-safe.** Import and export
401
+ price sensors remain compatible with the Energy dashboard, a timestamp entity
402
+ reports forecast coverage, and large forecast or optimizer payloads are not
403
+ recorded every minute. The fixed daily charge is no longer represented as a
404
+ marginal price sensor.
405
+ - **EMHASS forecasts are requested through an action and Predbat output is
406
+ opt-in.** Neither large payload is computed and attached to every entity when
407
+ unused.
408
+ - **Pricing provenance and quality are preserved end to end.** Core, CLI, REST,
409
+ MQTT, and Home Assistant outputs retain `locked`, `exact`, and `complete`
410
+ states and describe contiguous provenance segments across effective-date
411
+ boundaries.
412
+ - **Green Button names now describe the format rather than its container.**
413
+ `read_green_button`, `GreenButtonLayout`, and `--source green-button` replace
414
+ generic CSV terminology; the legacy source spelling remains accepted.
415
+ - **Strict typing now covers the Home Assistant custom component and all
416
+ repository tools**, while Ruff enforces function annotations throughout the
417
+ repository.
418
+
419
+ ### Fixed
420
+ - Production publishing no longer inherits a false implicit failure from the
421
+ intentionally skipped optional TestPyPI job. The explicit release dependency
422
+ checks now permit protected PyPI and GitHub publication only after the build
423
+ and draft jobs themselves succeed.
424
+ - Account-profile updates no longer race between revision checks and filesystem
425
+ mutation, statement-derived histories reject gaps and overlaps, imported Home
426
+ Assistant profiles cannot replace an entry's stable identity, and MQTT
427
+ profile selection respects environment precedence.
428
+ - Baseline credits use each day and vintage's own rate, pre-PTO exports earn no
429
+ compensation, string supplier values are normalized before branching, and
430
+ historical pricing never borrows data from a future vintage.
431
+ - CCA generation rates are selected by PG&E schedule instead of silently using
432
+ E-ELEC values for every plan. PCIA, franchise-fee, MCE generation, cost-relief,
433
+ and premium data are resolved from their actual effective vintages.
434
+ - Billing coverage and interval stepping use absolute time across both DST
435
+ transitions. Repeated autumn timestamps are disambiguated during ingest, rate
436
+ points remain contiguous, and MQTT no longer sleeps through the second 01:00
437
+ hour.
438
+ - Green Button ingestion handles account preambles, split date/time columns,
439
+ unit-suffixed headers, explicit offsets, and repeated autumn wall times.
440
+ Home Assistant and InfluxDB sources report implausible resets and missing
441
+ coverage instead of inventing plausible energy.
442
+ - Export-rate lookups use each vintage's own holiday calendar and surface
443
+ publisher drift beyond the verified exact range rather than silently claiming
444
+ exact future values.
445
+ - Generator parsing no longer drops a PCIA row joined to the next page header,
446
+ and all generated files are validated through independent runtime readers
447
+ before replacing vendored data.
448
+
449
+ ## [0.1.0] - 2026-07-28
450
+
451
+ Initial release.
452
+
453
+ ### Added
454
+ - `RateEngine` with `price_now()`, `price_at()`, and `forecast()` for PG&E
455
+ E-ELEC import prices and NEM 3.0 / Net Billing Tariff export credits.
456
+ - Vendored rate data for all five NBT vintages (NBT23/24/25/26/00), collapsed
457
+ from PG&E's ~40 MB-per-vintage hourly files to 268 KiB total with verified
458
+ lossless round-tripping.
459
+ - ACC Plus adder as a first-class, separately reported component.
460
+ - CCA / Direct Access support: bundled generation and PCIA are dropped, and
461
+ delivery-only prices are flagged `complete=False` rather than understated.
462
+ - CLI: `now`, `forecast`, `info`, `mqtt`, `serve`.
463
+ - MQTT publisher with Home Assistant MQTT Discovery and a last-will
464
+ availability topic.
465
+ - FastAPI service under the `web` extra.
466
+ - HACS-installable Home Assistant custom component.
467
+ - `tools/regen_data.py` for refreshing vendored data, plus a weekly CI job that
468
+ fails when upstream rates change.
469
+
470
+ ### Notes on upstream data
471
+ - PG&E's export files label the repeated 01:00 on the autumn DST transition as
472
+ `HS2`, so that hour is priced as 2am. Handled in `timeutil.export_hour`.
473
+ - From 2036 onward PG&E's own hour labels stop tracking Pacific daylight time
474
+ and NBT25/26/00 duplicate some holidays onto the following day. The verified
475
+ boundary is recorded per vintage as `exact_through` and surfaced as
476
+ `ExportPrice.exact`. Every year within a nine-year rate lock is exact.
477
+ - Holiday calendars are extracted per vintage from the source data rather than
478
+ recomputed, because the vintage files disagree in far-future years.
479
+
480
+ [Unreleased]: https://github.com/eman/tariffkit/compare/v0.4.0...HEAD
481
+ [0.4.0]: https://github.com/eman/tariffkit/releases/tag/v0.4.0
482
+ [0.3.0]: https://github.com/eman/tariffkit/releases/tag/v0.3.0
483
+ [0.2.3]: https://github.com/eman/tariffkit/releases/tag/v0.2.3
484
+ [0.2.2]: https://github.com/eman/tariffkit/releases/tag/v0.2.2
485
+ [0.2.1]: https://github.com/eman/tariffkit/releases/tag/v0.2.1
486
+ [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
3
+ Version: 0.4.0
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
@@ -52,15 +52,17 @@ Description-Content-Type: text/markdown
52
52
  [![CI](https://github.com/eman/tariffkit/actions/workflows/ci.yml/badge.svg)](https://github.com/eman/tariffkit/actions/workflows/ci.yml)
53
53
  [![PyPI](https://img.shields.io/pypi/v/tariffkit)](https://pypi.org/project/tariffkit/)
54
54
  [![Python 3.14.2](https://img.shields.io/badge/python-3.14.2-blue.svg)](https://www.python.org/downloads/)
55
- [![License: MIT](https://img.shields.io/badge/license-MIT-green.svg)](LICENSE)
55
+ [![License: MIT](https://img.shields.io/badge/license-MIT-green.svg)](https://github.com/eman/tariffkit/blob/main/LICENSE)
56
56
 
57
57
  An offline electricity tariff engine for pricing, billing, and energy-system
58
58
  integrations. The first data provider supports PG&E residential rate plans under
59
59
  **NEM 3.0 / the Net Billing Tariff (NBT)**; the package identity is deliberately
60
60
  not tied to one utility or tariff program.
61
61
 
62
- Three schedules are vendored: **E-ELEC** (Electric Home), **E-TOU-C**
63
- (Time-of-Use, peak 4–9 p.m. every day), and **EV2-A** (Home Charging).
62
+ The complete active single-family residential portfolio is vendored:
63
+ **E-1**, **E-ELEC**, **E-TOU-C**, **E-TOU-D**, and **EV2-A**. CARE, FERA,
64
+ Medical Baseline/D-MEDICAL, and event-injected SmartRate adjustments are
65
+ modeled separately from their underlying plans.
64
66
 
65
67
  Under NBT your export credit is not a time-of-use schedule: it is an hourly
66
68
  Avoided Cost Calculator value that swings from about $0.06/kWh at midday to
@@ -92,9 +94,9 @@ component. `tariffkit` collapses it at build time, verifying losslessness cell b
92
94
  cell, so the entire five-vintage dataset ships inside the wheel at **268 KiB**
93
95
  and every lookup is a few list indexes.
94
96
 
95
- The retail side is similar: these schedules' period boundaries are identical every day
96
- of the week including holidays and do not shift by season, so an import price is
97
- fully determined by `(season, hour)`.
97
+ The retail side is similar: generated effective-dated tables hold every active
98
+ schedule. E-TOU-D additionally selects weekday versus weekend/observed-holiday
99
+ periods from the vendored tariff calendar.
98
100
 
99
101
  Nothing here touches the network at runtime.
100
102
 
@@ -102,18 +104,20 @@ Nothing here touches the network at runtime.
102
104
 
103
105
  | | |
104
106
  |---|---|
105
- | [Configuration](docs/configuration.md) | Settings, CCA setup, reading your bill |
106
- | [Library](docs/library.md) | Embedding in Python |
107
- | [Named account profiles](docs/accounts.md) | Tracking a changing service agreement over time, importing PG&E statements |
108
- | [Bill calculator](docs/billing.md) | Computing a cycle from interval meter data |
109
- | [MQTT](docs/mqtt.md) | Publishing, with Home Assistant discovery |
110
- | [REST API](docs/web.md) | HTTP service |
111
- | [Home Assistant](docs/home-assistant.md) | Custom component, Energy dashboard, account history, response actions, opt-in Predbat |
112
- | [Home Assistant quality checklist](docs/home-assistant-quality.md) | Self-assessment against the Integration Quality Scale, with exemptions |
113
- | [Containers](docs/containers.md) | Local Home Assistant development stack and API/MQTT deployment proposal |
114
- | [Maintaining rate data](docs/data.md) | Regenerating export rates, updating the retail tariff and CCA cards |
115
- | [Packaging strategy](docs/packaging_strategy.md) | Architecture decision, boundaries, and release model |
116
- | [Release procedure](docs/releases.md) | Versioning, Trusted Publishing, verification, and recovery |
107
+ | [Configuration](https://github.com/eman/tariffkit/blob/main/docs/configuration.md) | Settings, CCA setup, reading your bill |
108
+ | [Library](https://github.com/eman/tariffkit/blob/main/docs/library.md) | Embedding in Python |
109
+ | [Named account profiles](https://github.com/eman/tariffkit/blob/main/docs/accounts.md) | Tracking a changing service agreement over time, importing PG&E statements |
110
+ | [Bill calculator](https://github.com/eman/tariffkit/blob/main/docs/billing.md) | Computing a cycle from interval meter data |
111
+ | [MQTT](https://github.com/eman/tariffkit/blob/main/docs/mqtt.md) | Publishing, with Home Assistant discovery |
112
+ | [REST API](https://github.com/eman/tariffkit/blob/main/docs/web.md) | HTTP service |
113
+ | [Home Assistant](https://github.com/eman/tariffkit/blob/main/docs/home-assistant.md) | Custom component, Energy dashboard, stacked component charts, account history, response actions, opt-in Predbat |
114
+ | [Predbat](https://github.com/eman/tariffkit/blob/main/docs/predbat.md) | Installing TariffKit and Predbat together, end to end |
115
+ | [Predbat on Sigenergy](https://github.com/eman/tariffkit/blob/main/docs/predbat-sigenergy.md) | Sigenergy SigenStor specifics: entity mapping, sign and unit conversions, control |
116
+ | [Home Assistant quality checklist](https://github.com/eman/tariffkit/blob/main/docs/home-assistant-quality.md) | Self-assessment against the Integration Quality Scale, with exemptions |
117
+ | [Containers](https://github.com/eman/tariffkit/blob/main/docs/containers.md) | Local Home Assistant development stack and API/MQTT deployment proposal |
118
+ | [Maintaining rate data](https://github.com/eman/tariffkit/blob/main/docs/data.md) | Regenerating export rates, updating the retail tariff and CCA cards |
119
+ | [Packaging strategy](https://github.com/eman/tariffkit/blob/main/docs/packaging_strategy.md) | Architecture decision, boundaries, and release model |
120
+ | [Release procedure](https://github.com/eman/tariffkit/blob/main/docs/releases.md) | Versioning, Trusted Publishing, verification, and recovery |
117
121
 
118
122
  ## Works with
119
123
 
@@ -129,8 +133,8 @@ Predbat opt-in.
129
133
  | **EMHASS** | `tariffkit.get_emhass_forecast` action, called with any window | `load_cost_forecast` / `prod_price_forecast` attributes, always published |
130
134
  | **Predbat** | `raw_today` / `raw_tomorrow` attributes, only once enabled in options | `raw_today` / `raw_tomorrow` attributes, always published |
131
135
 
132
- See [docs/home-assistant.md](docs/home-assistant.md) and
133
- [docs/mqtt.md](docs/mqtt.md) for setup of each.
136
+ See [docs/home-assistant.md](https://github.com/eman/tariffkit/blob/main/docs/home-assistant.md) and
137
+ [docs/mqtt.md](https://github.com/eman/tariffkit/blob/main/docs/mqtt.md) for setup of each.
134
138
 
135
139
  ## Install
136
140
 
@@ -190,7 +194,7 @@ base_services_charge_tier = 3
190
194
  `TARIFFKIT_*` environment variables override any file setting.
191
195
  Long-lived PG&E, Home Assistant, InfluxDB, and MQTT credentials can be stored
192
196
  outside that file with `tariffkit credentials set`; see
193
- [Configuration](docs/configuration.md#credentials).
197
+ [Configuration](https://github.com/eman/tariffkit/blob/main/docs/configuration.md#credentials).
194
198
 
195
199
  ### What the numbers include
196
200
 
@@ -245,7 +249,7 @@ franchise_fee_surcharge = 0.00042 # $/kWh, from your bill
245
249
  ```
246
250
 
247
251
  For other CCAs, supply `generation_rates` directly; see
248
- [docs/configuration.md](docs/configuration.md). Until generation rates and a
252
+ [docs/configuration.md](https://github.com/eman/tariffkit/blob/main/docs/configuration.md). Until generation rates and a
249
253
  franchise fee are configured, CCA mode returns delivery-only prices flagged
250
254
  `complete = False` rather than quietly understating your rates.
251
255
 
@@ -274,11 +278,11 @@ rather than as silent drift. Export files are updated by **October 1** of any
274
278
  year the CPUC adopts a new Avoided Cost Calculator; retail rates change more
275
279
  often, via advice letters — three times in the first half of 2026 alone — which
276
280
  is why a superseded vintage can be rebuilt from the filing that adopted it. See
277
- [docs/data.md](docs/data.md).
281
+ [docs/data.md](https://github.com/eman/tariffkit/blob/main/docs/data.md).
278
282
 
279
283
  ## Data sources
280
284
 
281
- See [docs/data.md](docs/data.md#data-sources) for the source of every vendored
285
+ See [docs/data.md](https://github.com/eman/tariffkit/blob/main/docs/data.md#data-sources) for the source of every vendored
282
286
  table and why OpenEI's URDB is deliberately not used.
283
287
 
284
288
  ## License