PRICE-01: price_service provider, fallback chain, and the gate read from it
A new price source, `price_provider: price_service`: the owner's price server,
one fixed HTTPS host, a bearer token (new password option
`price_service_token`), wholesale energy only so markup and taxes are added as
for generic_dayahead.
The chain is server, then energy-charts, then the prices the history store
kept. A step counts only if it has a price for now. Each step down and back up
is one log line. With every source down a site plans from what it saved.
No price for now: one status row, one Home plan line and one notification, in
English and Dutch, cleared when a price returns. With this provider the
maintenance charge gate reads the provider's prices and keeps the Home
Assistant rule: the current 15-minute price is at or below today's average.
The token is hidden on the settings page, refused when missing, and taken out
of any error text. Other providers, `off` and the Home Assistant pair take the
road they always took (scripted-day probe against 0ce7d35: one line differs,
the refusal that lists the known providers). See
DECISIONS/PRICE-01-own-price-server.md.
Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Bdqr7PiaHcBnzPituu2yTC
This commit is contained in:
co-authored by
Claude
parent
0ce7d35f80
commit
9ac3a9d2bc
@@ -534,11 +534,15 @@ FIXTURES = {
|
||||
[v / 1000 for v in EPEX_EUR_MWH]),
|
||||
"custom_formula": (columnar_feed(EPEX_EUR_MWH), FORMULA_EUR_KWH),
|
||||
"manual_csv": (None, [0.1834, -0.0210, 0.3115]),
|
||||
# PRICE-01: the owner's price server answers the same columnar layout.
|
||||
"price_service": (columnar_feed(EPEX_EUR_MWH),
|
||||
[v / 1000 for v in EPEX_EUR_MWH]),
|
||||
}
|
||||
|
||||
check(f"the plugins that ship after TARIFF-07 and TARIFF-15 are registered "
|
||||
f"{sorted(PROVIDERS)}",
|
||||
set(PROVIDERS) == {"generic_dayahead", "custom_formula", "manual_csv"})
|
||||
set(PROVIDERS) == {"generic_dayahead", "custom_formula", "manual_csv",
|
||||
"price_service"})
|
||||
check(f"...and the three withdrawn ids resolve to NOTHING "
|
||||
f"{sorted(RETIRED_PROVIDERS)}",
|
||||
set(RETIRED_PROVIDERS) == {"engie_dynamic", "totalenergies_dynamic",
|
||||
@@ -571,7 +575,7 @@ def canned(body):
|
||||
"""A transport that serves recorded bytes and records that it was used."""
|
||||
calls = []
|
||||
|
||||
async def t(url, timeout_s):
|
||||
async def t(url, timeout_s, headers=None):
|
||||
calls.append((url, timeout_s))
|
||||
return body
|
||||
t.calls = calls
|
||||
@@ -603,6 +607,9 @@ def load_any(pid):
|
||||
# is exactly what section 3 grades.
|
||||
return load(pid, {"price_formula": json.dumps(ENECO_CARD)},
|
||||
transport=canned(body))
|
||||
if pid == "price_service":
|
||||
return load(pid, {"price_service_token": "test-token-1234"},
|
||||
transport=canned(body))
|
||||
return load(pid, transport=canned(body))
|
||||
|
||||
|
||||
@@ -898,7 +905,16 @@ _networked = sorted(pid for pid, cls in PROVIDERS.items()
|
||||
# SAME CAPTURED BYTES (section 13c), because it reads the same endpoint with
|
||||
# the same parser. A networked plugin with no capture is what this line exists
|
||||
# to forbid.
|
||||
_replayed = ["custom_formula", "generic_dayahead"]
|
||||
#
|
||||
# ⚠️ PRICE-01 PUT A THIRD ON IT, AND ITS CAPTURE IS A RESHAPE, SAID PLAINLY. The
|
||||
# price server answers only to a token this session does not hold, so no real
|
||||
# answer of its own exists to replay. Its answer is the energy-charts layout
|
||||
# (design, API section), so test_price_service.py replays the REAL energy-charts
|
||||
# two-day capture with the three extra keys the design lists (`covers`,
|
||||
# `source`, `fetched_at`). The prices and instants are real; the wrapper is the
|
||||
# design's. The first answer captured from the live server on the rig test
|
||||
# replaces it - that is the open item, and it is in the PRICE-01 report.
|
||||
_replayed = ["custom_formula", "generic_dayahead", "price_service"]
|
||||
check(f"the networked plugins are {_networked}, and EVERY one of them is "
|
||||
f"replayed from a captured real body ({_replayed}) - no shipped plugin "
|
||||
f"reaches a network this file has never seen answer (TARIFF-07)",
|
||||
@@ -1252,7 +1268,7 @@ check(f"a negative day-ahead price passes through, all-in {_neg}",
|
||||
# (TARIFF-15): it reads the SAME columnar document, which has no per-point
|
||||
# flag, and applies arithmetic to it - arithmetic cannot invent a provenance
|
||||
# the document never carried.
|
||||
_SETTLED_FEED = {"generic_dayahead", "custom_formula"}
|
||||
_SETTLED_FEED = {"generic_dayahead", "custom_formula", "price_service"}
|
||||
for pid in sorted(set(READS) - _SETTLED_FEED):
|
||||
read = READS[pid]
|
||||
flags = derived(lambda r=read: [p.provisional for p in r.points])
|
||||
|
||||
Reference in New Issue
Block a user