M8-T16: add thermal meter cost APIs

This commit is contained in:
2026-08-23 21:22:06 +02:00
parent 489e5b596a
commit 3eec701448
12 changed files with 1843 additions and 19 deletions
+18 -8
View File
@@ -220,7 +220,14 @@ def compute_closed_periods(session: Session, *, now: datetime | None = None) ->
return written
def recompute_range(session: Session, start: datetime, end: datetime) -> int:
def recompute_range(session: Session, start: datetime, end: datetime, *, commit: bool = True) -> int:
"""Recompute a thermal range.
The historical service entry point remains self-committing for the scheduler
and direct callers. HTTP callers pass ``commit=False`` so validation,
recomputation, response statistics, and the single commit share one
transaction owned by the route.
"""
cursor, end = floor_to_quarter(_utc(start)), _utc(end)
now, written = datetime.now(UTC), 0
while cursor < end:
@@ -229,7 +236,8 @@ def recompute_range(session: Session, start: datetime, end: datetime) -> int:
if compute_period(session, commodity, cursor, overwrite=True):
written += 1
cursor += _PERIOD
session.commit()
if commit:
session.commit()
return written
@@ -257,7 +265,9 @@ def summarize(session: Session, start: datetime, end: datetime, *, now: datetime
final_day = timezone_service.local_date(end - timedelta(microseconds=1))
final_day = min(final_day, _settled_end_date(now or datetime.now(UTC)))
day = timezone_service.local_date(start)
fixed = Decimal("0")
fixed_breakdown: dict[str, Decimal] = {key: Decimal("0") for key in (
"heating_network", "metering", "delivery_set", "hot_water_network", "other"
)}
versions = active_contract_versions(session, scope="thermal")
while start < end and day <= final_day:
day_start = datetime.combine(day, time.min, tzinfo=timezone_service.local_tz()).astimezone(UTC)
@@ -275,14 +285,14 @@ def summarize(session: Session, start: datetime, end: datetime, *, now: datetime
if segment_start >= segment_end:
continue
values = version.values["standing"]
annual = sum((_decimal(values.get(key, "0")) for key in (
"heating_network", "metering", "delivery_set", "hot_water_network", "other"
)), Decimal("0"))
fraction = Decimal(str((segment_end - segment_start).total_seconds())) / local_day_seconds
fixed += annual / Decimal("365") * fraction
for key in fixed_breakdown:
fixed_breakdown[key] += _decimal(values.get(key, "0")) / Decimal("365") * fraction
day += timedelta(days=1)
fixed = sum(fixed_breakdown.values(), Decimal("0"))
return {
"currency": good[0].currency if good else "EUR", "variable_cost": variable,
"fixed_cost": fixed, "total_cost": variable + fixed, "breakdown": breakdown,
"fixed_cost": fixed, "fixed_breakdown": fixed_breakdown,
"total_cost": variable + fixed, "breakdown": breakdown,
"period_count": len(good), "degraded_count": len(rows) - len(good),
}