Handle ENLYZE timeseries pagination

This commit is contained in:
2026-09-05 08:19:17 +02:00
parent 6ddb76fd03
commit 7c029759f5
2 changed files with 150 additions and 46 deletions
+62 -46
View File
@@ -68,52 +68,68 @@ class EnlyzeApiGateway:
raise ValueError(f"{name} must be timezone-aware")
if end < start:
raise ValueError("end must not precede start")
response = self._client.post_json(
"/v2/timeseries",
{
"machine": machine_id,
"start": start.astimezone(UTC).isoformat(),
"end": end.astimezone(UTC).isoformat(),
"variables": [
{"uuid": rate_variable_id},
{"uuid": gate_variable_id},
],
},
)
try:
data = response.body["data"]
columns = data["columns"]
if not isinstance(columns, list):
raise ValueError("columns must be a list")
for required in ("time", rate_variable_id, gate_variable_id):
if columns.count(required) != 1:
raise ValueError(f"required column {required!r} must occur exactly once")
time_index = columns.index("time")
rate_index = columns.index(rate_variable_id)
gate_index = columns.index(gate_variable_id)
if not isinstance(data["records"], list):
raise ValueError("records must be a list")
samples = []
for index, record in enumerate(data["records"]):
try:
if not isinstance(record, list) or len(record) != len(columns):
raise ValueError("record must match columns")
rate, gate = record[rate_index], record[gate_index]
for value in (rate, gate):
if isinstance(value, bool) or not isinstance(value, (int, float)):
raise ValueError("rate and gate must be numeric")
if not isfinite(value):
raise ValueError("rate and gate must be finite")
samples.append(MaterialSample(
timestamp=_parse_timestamp(record[time_index]),
material_rate_kg_per_hour=float(rate), gate_value=float(gate),
))
except (TypeError, ValueError) as exc:
raise ValueError(f"malformed record {index}: {exc}") from exc
return samples
except (KeyError, TypeError, ValueError) as exc:
raise ValueError(f"Invalid timeseries response: {exc}") from exc
request_body = {
"machine": machine_id,
"start": start.astimezone(UTC).isoformat(),
"end": end.astimezone(UTC).isoformat(),
"variables": [
{"uuid": rate_variable_id},
{"uuid": gate_variable_id},
],
}
samples = []
followed_cursors: set[str] = set()
page = 1
while True:
response = self._client.post_json("/v2/timeseries", request_body)
try:
data = response.body["data"]
columns = data["columns"]
if not isinstance(columns, list):
raise ValueError("columns must be a list")
for required in ("time", rate_variable_id, gate_variable_id):
if columns.count(required) != 1:
raise ValueError(f"required column {required!r} must occur exactly once")
time_index = columns.index("time")
rate_index = columns.index(rate_variable_id)
gate_index = columns.index(gate_variable_id)
if not isinstance(data["records"], list):
raise ValueError("records must be a list")
for index, record in enumerate(data["records"]):
try:
if not isinstance(record, list) or len(record) != len(columns):
raise ValueError("record must match columns")
rate, gate = record[rate_index], record[gate_index]
for value in (rate, gate):
if isinstance(value, bool) or not isinstance(value, (int, float)):
raise ValueError("rate and gate must be numeric")
if not isfinite(value):
raise ValueError("rate and gate must be finite")
samples.append(MaterialSample(
timestamp=_parse_timestamp(record[time_index]),
material_rate_kg_per_hour=float(rate), gate_value=float(gate),
))
except (TypeError, ValueError) as exc:
raise ValueError(f"malformed record {index}: {exc}") from exc
next_cursor = None
if "metadata" in response.body:
metadata = response.body["metadata"]
if not isinstance(metadata, dict) or "next_cursor" not in metadata:
raise ValueError("metadata must be an object with a next_cursor field")
next_cursor = metadata["next_cursor"]
if next_cursor is not None and (
not isinstance(next_cursor, str) or not next_cursor
):
raise ValueError("next_cursor must be null or a non-empty string")
if next_cursor is None:
return samples
if next_cursor in followed_cursors:
raise ValueError("next_cursor has already been followed")
followed_cursors.add(next_cursor)
request_body = {**request_body, "cursor": next_cursor}
page += 1
except (KeyError, TypeError, ValueError) as exc:
raise ValueError(f"Invalid timeseries response on page {page}: {exc}") from exc
def _parse_timestamp(value: str) -> datetime: