expose: fold standing fee into import cost, tax credit into export revenue
So HA can show the full payable: net = import - export = Total Payable, with both entities staying monotonic-positive (no negative-value/reset pitfalls). - import_cost_total = SUM(import_cost) + days*(network_fee+management_fee)/30 - export_revenue_total = SUM(export_revenue) + days*heffingskorting/365 (days = calendar days since the active version's effective_from, incl. today) - state_class total_increasing -> total (monetary-compatible; values still only increase). device_class/key/names: key unchanged, names clarified. - buy/sell_price_now and DeviceInfo untouched. Decimal money math. Tests added.
This commit is contained in:
+65
-10
@@ -511,12 +511,23 @@ def _energy_cost_provider(session: Session) -> list[ExposableEntity]:
|
||||
|
||||
return _getter
|
||||
|
||||
# --- value_getter: cumulative import cost ---
|
||||
# --- value_getter: cumulative import cost (incl. standing charges) ---
|
||||
|
||||
def _make_import_cost_getter() -> Callable[["Session"], Any]:
|
||||
"""Return a getter for the cumulative import cost (non-degraded rows only)."""
|
||||
"""Return a getter for the cumulative import cost plus prorated standing charges.
|
||||
|
||||
Value = SUM(import_cost, non-degraded) + standing_charges_cumulative
|
||||
|
||||
where standing_charges_cumulative = elapsed_days × (network_fee + management_fee) / 30.
|
||||
Elapsed days are counted from the active contract version's effective_from date
|
||||
(inclusive of today). Returns None when no non-degraded periods exist.
|
||||
"""
|
||||
def _getter(sess: "Session") -> Any:
|
||||
from decimal import Decimal
|
||||
from datetime import UTC, datetime as _dt
|
||||
|
||||
from app.models.energy import EnergyCostPeriod as _ECP
|
||||
from app.services.contracts import active_contract_version_at, _as_utc
|
||||
from sqlalchemy import func as _func
|
||||
|
||||
total = sess.query(_func.sum(_ECP.import_cost)).filter(
|
||||
@@ -524,16 +535,44 @@ def _energy_cost_provider(session: Session) -> list[ExposableEntity]:
|
||||
).scalar()
|
||||
if total is None:
|
||||
return None
|
||||
return float(total)
|
||||
|
||||
# Add prorated standing charges from the active contract version.
|
||||
standing_cumulative = Decimal("0")
|
||||
now_utc = _dt.now(UTC)
|
||||
version = active_contract_version_at(sess, now_utc)
|
||||
if version is not None:
|
||||
vals = version.values or {}
|
||||
standing = vals.get("standing", {})
|
||||
network_fee = Decimal(str(standing.get("network_fee") or 0))
|
||||
management_fee = Decimal(str(standing.get("management_fee") or 0))
|
||||
daily_standing = (network_fee + management_fee) / Decimal("30")
|
||||
|
||||
effective_from = _as_utc(version.effective_from)
|
||||
days = (now_utc.date() - effective_from.date()).days + 1
|
||||
days = max(days, 0)
|
||||
standing_cumulative = daily_standing * days
|
||||
|
||||
return float(Decimal(str(total)) + standing_cumulative)
|
||||
|
||||
return _getter
|
||||
|
||||
# --- value_getter: cumulative export revenue ---
|
||||
# --- value_getter: cumulative export revenue (incl. tax credit) ---
|
||||
|
||||
def _make_export_revenue_getter() -> Callable[["Session"], Any]:
|
||||
"""Return a getter for the cumulative export revenue (non-degraded rows only)."""
|
||||
"""Return a getter for the cumulative export revenue plus prorated tax credit.
|
||||
|
||||
Value = SUM(export_revenue, non-degraded) + heffingskorting_cumulative
|
||||
|
||||
where heffingskorting_cumulative = elapsed_days × heffingskorting / 365.
|
||||
Elapsed days are counted from the active contract version's effective_from date
|
||||
(inclusive of today). Returns None when no non-degraded periods exist.
|
||||
"""
|
||||
def _getter(sess: "Session") -> Any:
|
||||
from decimal import Decimal
|
||||
from datetime import UTC, datetime as _dt
|
||||
|
||||
from app.models.energy import EnergyCostPeriod as _ECP
|
||||
from app.services.contracts import active_contract_version_at, _as_utc
|
||||
from sqlalchemy import func as _func
|
||||
|
||||
total = sess.query(_func.sum(_ECP.export_revenue)).filter(
|
||||
@@ -541,7 +580,23 @@ def _energy_cost_provider(session: Session) -> list[ExposableEntity]:
|
||||
).scalar()
|
||||
if total is None:
|
||||
return None
|
||||
return float(total)
|
||||
|
||||
# Add prorated heffingskorting credit from the active contract version.
|
||||
credit_cumulative = Decimal("0")
|
||||
now_utc = _dt.now(UTC)
|
||||
version = active_contract_version_at(sess, now_utc)
|
||||
if version is not None:
|
||||
vals = version.values or {}
|
||||
credits = vals.get("credits", {})
|
||||
heffingskorting = Decimal(str(credits.get("heffingskorting") or 0))
|
||||
daily_credit = heffingskorting / Decimal("365")
|
||||
|
||||
effective_from = _as_utc(version.effective_from)
|
||||
days = (now_utc.date() - effective_from.date()).days + 1
|
||||
days = max(days, 0)
|
||||
credit_cumulative = daily_credit * days
|
||||
|
||||
return float(Decimal(str(total)) + credit_cumulative)
|
||||
|
||||
return _getter
|
||||
|
||||
@@ -575,9 +630,9 @@ def _energy_cost_provider(session: Session) -> list[ExposableEntity]:
|
||||
device=device_info,
|
||||
device_class="monetary",
|
||||
unit=currency,
|
||||
name="Energy Import Cost Total",
|
||||
name="Energy Import Cost (incl. standing)",
|
||||
value_getter=_make_import_cost_getter(),
|
||||
state_class="total_increasing",
|
||||
state_class="total",
|
||||
),
|
||||
ExposableEntity(
|
||||
key="energy.export_revenue_total",
|
||||
@@ -585,9 +640,9 @@ def _energy_cost_provider(session: Session) -> list[ExposableEntity]:
|
||||
device=device_info,
|
||||
device_class="monetary",
|
||||
unit=currency,
|
||||
name="Energy Export Revenue Total",
|
||||
name="Energy Export Revenue (incl. tax credit)",
|
||||
value_getter=_make_export_revenue_getter(),
|
||||
state_class="total_increasing",
|
||||
state_class="total",
|
||||
),
|
||||
]
|
||||
|
||||
|
||||
Reference in New Issue
Block a user