M8-R03B: keep meter swap recompute in caller transaction
This commit is contained in:
@@ -151,7 +151,7 @@ def _trigger_recompute(db: Session, start: datetime, label: str) -> int:
|
||||
# started_at is in the future — nothing to recompute.
|
||||
logger.info("%s: started_at (%s) is in the future, skipping recompute.", label, start)
|
||||
return 0
|
||||
n = recompute_range(db, start, end)
|
||||
n = recompute_range(db, start, end, commit=False)
|
||||
logger.info(
|
||||
"%s: recomputed %d period(s) in window [%s, %s).",
|
||||
label,
|
||||
@@ -352,23 +352,27 @@ def patch_energy_meter(
|
||||
note=body.note,
|
||||
started_at=new_started_at_utc,
|
||||
)
|
||||
|
||||
# Retroactive recompute if started_at was changed.
|
||||
if new_started_at_utc is not None and old_started_at is not None:
|
||||
# Normalise old_started_at to UTC-aware for comparison.
|
||||
if old_started_at.tzinfo is None:
|
||||
old_started_at = old_started_at.replace(tzinfo=UTC)
|
||||
# Window = [min(old, new), now) — covers all periods whose attribution
|
||||
# may have changed due to the boundary shift in either direction.
|
||||
window_start = min(old_started_at, new_started_at_utc)
|
||||
_trigger_recompute(db, window_start, f"PATCH /api/energy/meters/{meter_id}")
|
||||
|
||||
db.commit()
|
||||
except MeterIntervalError as exc:
|
||||
db.rollback()
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
|
||||
detail=str(exc),
|
||||
)
|
||||
|
||||
# Retroactive recompute if started_at was changed.
|
||||
if new_started_at_utc is not None and old_started_at is not None:
|
||||
# Normalise old_started_at to UTC-aware for comparison.
|
||||
if old_started_at.tzinfo is None:
|
||||
old_started_at = old_started_at.replace(tzinfo=UTC)
|
||||
# Window = [min(old, new), now) — covers all periods whose attribution
|
||||
# may have changed due to the boundary shift in either direction.
|
||||
window_start = min(old_started_at, new_started_at_utc)
|
||||
_trigger_recompute(db, window_start, f"PATCH /api/energy/meters/{meter_id}")
|
||||
|
||||
db.commit()
|
||||
except Exception:
|
||||
db.rollback()
|
||||
raise
|
||||
db.refresh(meter)
|
||||
|
||||
# Trigger HA discovery re-publish so label renames on the active meter
|
||||
|
||||
@@ -665,7 +665,9 @@ def compute_closed_periods(session: Session) -> int:
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
def recompute_range(session: Session, start: datetime, end: datetime) -> int:
|
||||
def recompute_range(
|
||||
session: Session, start: datetime, end: datetime, *, commit: bool = True
|
||||
) -> int:
|
||||
"""Recompute (overwrite) all 15-minute periods in ``[start, end)``.
|
||||
|
||||
This is the *explicit opt-in* path for recovering from:
|
||||
@@ -686,8 +688,11 @@ def recompute_range(session: Session, start: datetime, end: datetime) -> int:
|
||||
Parameters
|
||||
----------
|
||||
session:
|
||||
Active SQLAlchemy session. The function commits after all periods
|
||||
have been processed.
|
||||
Active SQLAlchemy session.
|
||||
commit:
|
||||
When true (the default), commit after all periods have been processed.
|
||||
Callers composing this recompute with other writes may pass false and
|
||||
own the surrounding transaction themselves.
|
||||
start:
|
||||
Inclusive start datetime (floored to the nearest quarter-hour internally).
|
||||
end:
|
||||
@@ -724,7 +729,8 @@ def recompute_range(session: Session, start: datetime, end: datetime) -> int:
|
||||
)
|
||||
t0 += timedelta(minutes=_PERIOD_MINUTES)
|
||||
|
||||
session.commit()
|
||||
if commit:
|
||||
session.commit()
|
||||
logger.info(
|
||||
"recompute_range(%s, %s): wrote %d period(s).",
|
||||
start.isoformat(),
|
||||
|
||||
Reference in New Issue
Block a user