fix: validate complete-day monitoring coverage
This commit is contained in:
+22
-31
@@ -33,6 +33,11 @@ from itertools import pairwise
|
||||
from typing import Any
|
||||
from zoneinfo import ZoneInfo
|
||||
|
||||
from .monitoring_periods import (
|
||||
as_utc_datetime,
|
||||
interval_within_one_monitoring_period,
|
||||
)
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class LocalDaySummary:
|
||||
@@ -308,27 +313,13 @@ def persist_local_day(
|
||||
return created
|
||||
|
||||
|
||||
def _utc_datetime(value: str | datetime) -> datetime:
|
||||
parsed = value if isinstance(value, datetime) else datetime.fromisoformat(value)
|
||||
if parsed.tzinfo is None:
|
||||
parsed = parsed.replace(tzinfo=timezone.utc)
|
||||
return parsed.astimezone(timezone.utc)
|
||||
|
||||
|
||||
def _same_monitoring_period(
|
||||
conn: sqlite3.Connection,
|
||||
start: datetime,
|
||||
end: datetime,
|
||||
) -> bool:
|
||||
"""Return whether one recorded monitoring period contains the interval."""
|
||||
for started_at, ended_at in conn.execute(
|
||||
"SELECT started_at, ended_at FROM monitoring_periods"
|
||||
):
|
||||
period_start = _utc_datetime(started_at)
|
||||
period_end = _utc_datetime(ended_at) if ended_at else None
|
||||
if period_start <= start and (period_end is None or end <= period_end):
|
||||
return True
|
||||
return False
|
||||
return interval_within_one_monitoring_period(conn, start, end)
|
||||
|
||||
|
||||
def repair_legacy_local_day_evidence(conn: sqlite3.Connection) -> int:
|
||||
@@ -365,13 +356,13 @@ def repair_legacy_local_day_evidence(conn: sqlite3.Connection) -> int:
|
||||
segment_windows = {}
|
||||
if {"monitoring_periods", "controller_segments"}.issubset(tables):
|
||||
periods = [
|
||||
(_utc_datetime(start), _utc_datetime(end) if end else None)
|
||||
(as_utc_datetime(start), as_utc_datetime(end) if end else None)
|
||||
for start, end in conn.execute(
|
||||
"SELECT started_at, ended_at FROM monitoring_periods"
|
||||
)
|
||||
]
|
||||
segment_boundaries = [
|
||||
(segment_id, _utc_datetime(opened_at))
|
||||
(segment_id, as_utc_datetime(opened_at))
|
||||
for segment_id, opened_at in conn.execute(
|
||||
"SELECT id, opened_at FROM controller_segments ORDER BY opened_at, id"
|
||||
)
|
||||
@@ -399,7 +390,7 @@ def repair_legacy_local_day_evidence(conn: sqlite3.Connection) -> int:
|
||||
)
|
||||
samples = [
|
||||
{
|
||||
"id": row[0], "ts": _utc_datetime(row[1]),
|
||||
"id": row[0], "ts": as_utc_datetime(row[1]),
|
||||
"bytes_written": row[2], "bytes_read": row[3],
|
||||
"segment_id": row[4], "local_tz": row[5],
|
||||
}
|
||||
@@ -453,8 +444,8 @@ def repair_legacy_local_day_evidence(conn: sqlite3.Connection) -> int:
|
||||
"id": row[0],
|
||||
"local_date": row[1],
|
||||
"tz_name": row[2],
|
||||
"start": _utc_datetime(row[3]),
|
||||
"end": _utc_datetime(row[4]),
|
||||
"start": as_utc_datetime(row[3]),
|
||||
"end": as_utc_datetime(row[4]),
|
||||
"precision": row[6],
|
||||
}
|
||||
for row in recorded_days
|
||||
@@ -482,8 +473,8 @@ def repair_legacy_local_day_evidence(conn: sqlite3.Connection) -> int:
|
||||
local_day_id, local_date, tz_name, utc_start, utc_end,
|
||||
prior_incomplete,
|
||||
) = row
|
||||
start = _utc_datetime(utc_start)
|
||||
end = _utc_datetime(utc_end)
|
||||
start = as_utc_datetime(utc_start)
|
||||
end = as_utc_datetime(utc_end)
|
||||
matching = matching_by_day[local_day_id]
|
||||
|
||||
if not matching:
|
||||
@@ -595,8 +586,8 @@ def _record_reconstructed_shared_intervals(
|
||||
day_rows = [
|
||||
{
|
||||
"id": row[0], "local_date": row[1], "tz_name": row[2],
|
||||
"utc_start": _utc_datetime(row[3]),
|
||||
"utc_end": _utc_datetime(row[4]),
|
||||
"utc_start": as_utc_datetime(row[3]),
|
||||
"utc_end": as_utc_datetime(row[4]),
|
||||
"precision": row[6],
|
||||
}
|
||||
for row in recorded_days
|
||||
@@ -685,7 +676,7 @@ def _coarse_local_day_activity(
|
||||
full_hours: dict[datetime, tuple] = {}
|
||||
boundary_hour_found = False
|
||||
other_day_bounds = [
|
||||
(_utc_datetime(row[0]), _utc_datetime(row[1]))
|
||||
(as_utc_datetime(row[0]), as_utc_datetime(row[1]))
|
||||
for row in conn.execute(
|
||||
"SELECT utc_start, utc_end FROM local_days WHERE id != ?",
|
||||
(local_day_id,),
|
||||
@@ -698,7 +689,7 @@ def _coarse_local_day_activity(
|
||||
"FROM hour_observations WHERE hour >= ? AND hour < ? ORDER BY hour",
|
||||
(first_hour.isoformat(), day_end.isoformat()),
|
||||
):
|
||||
hour_start = _utc_datetime(row[0])
|
||||
hour_start = as_utc_datetime(row[0])
|
||||
hour_end = hour_start + hour
|
||||
if hour_start >= day_end or hour_end <= day_start:
|
||||
continue
|
||||
@@ -856,8 +847,8 @@ def record_local_activity_interval(
|
||||
both samples share one monitoring period, and the interval stays inside one
|
||||
recorded local date. Every other valid difference remains one unallocated row.
|
||||
"""
|
||||
start = _utc_datetime(previous["ts"])
|
||||
end = _utc_datetime(current["ts"])
|
||||
start = as_utc_datetime(previous["ts"])
|
||||
end = as_utc_datetime(current["ts"])
|
||||
if end <= start:
|
||||
return None
|
||||
|
||||
@@ -1004,12 +995,12 @@ def mark_local_activity_gap(
|
||||
previous: dict | None = None,
|
||||
) -> None:
|
||||
"""Mark dates around a controller boundary as missing local activity."""
|
||||
current = _utc_datetime(current_ts)
|
||||
current = as_utc_datetime(current_ts)
|
||||
current_date = current.astimezone(ZoneInfo(current_tz)).date().isoformat()
|
||||
_upsert_activity_day(conn, current_date, current_tz, incomplete=True)
|
||||
if previous is None or not previous.get("local_tz"):
|
||||
return
|
||||
previous_at = _utc_datetime(previous["ts"])
|
||||
previous_at = as_utc_datetime(previous["ts"])
|
||||
previous_tz = previous["local_tz"]
|
||||
previous_date = previous_at.astimezone(ZoneInfo(previous_tz)).date().isoformat()
|
||||
_upsert_activity_day(conn, previous_date, previous_tz, incomplete=True)
|
||||
@@ -1127,7 +1118,7 @@ def query_local_day_summary(
|
||||
== recorded_date
|
||||
)
|
||||
day_seconds = int(
|
||||
(_utc_datetime(utc_end) - _utc_datetime(utc_start)).total_seconds()
|
||||
(as_utc_datetime(utc_end) - as_utc_datetime(utc_start)).total_seconds()
|
||||
)
|
||||
interval_seconds = activity_seconds or 0
|
||||
full_activity_day = interval_seconds >= day_seconds or (
|
||||
|
||||
Reference in New Issue
Block a user