Show trustworthy local-day activity totals (#90)

Add local-day activity summaries derived from UTC hour observations using
the system timezone, with durable storage in a new local_days table.
The collector derives local-day read/write totals after UTC aggregation;
the TUI displays them with timezone, completeness state, and coverage.

Schema: bump SCHEMA_VERSION to 3, add local_days table (migration 2→3
is pure addition, idempotent, preserves newer-schema refusal).
This commit is contained in:
xavierk
2026-09-18 12:28:23 +05:30
parent 366c2f55b7
commit 4f884b4b73
8 changed files with 834 additions and 3 deletions
+74
View File
@@ -1045,19 +1045,35 @@ class FenrisTuiApp(App):
grid-rows: auto 10 auto 3;
height: auto;
}
#main-grid.local-day {
grid-size: 2 5;
grid-rows: auto 10 auto auto 3;
}
#main-grid.paused {
grid-size: 2 5;
grid-rows: auto auto 10 auto 3;
}
#main-grid.paused.local-day {
grid-size: 2 6;
grid-rows: auto auto 10 auto auto 3;
}
#main-grid.constrained {
grid-size: 1 4;
grid-columns: 1fr;
grid-rows: auto auto auto auto;
}
#main-grid.constrained.local-day {
grid-size: 1 5;
grid-rows: auto auto auto auto auto;
}
#main-grid.constrained.paused {
grid-size: 1 5;
grid-rows: auto auto auto auto auto;
}
#main-grid.constrained.paused.local-day {
grid-size: 1 6;
grid-rows: auto auto auto auto auto auto;
}
#main-grid.constrained #usage-history {
display: none;
}
@@ -1071,6 +1087,14 @@ class FenrisTuiApp(App):
height: auto;
min-height: 3;
}
#local-day {
column-span: 2;
display: none;
border: round #555555;
padding: 0 1;
height: auto;
min-height: 3;
}
#dashboard-scroll { height: 1fr; }
#headline-band { column-span: 2; height: auto; min-height: 8; }
#paused-banner {
@@ -1141,6 +1165,7 @@ class FenrisTuiApp(App):
yield DailyBarGraph(id="usage-history", classes="pane")
yield Static("", id="constrained-summary", classes="pane")
yield Static("", id="drive-health", classes="pane")
yield Static("", id="local-day", classes="pane")
yield Static("", id="service-strip", classes="pane")
yield Static("q Quit TUI", id="quit-rail")
@@ -1154,6 +1179,7 @@ class FenrisTuiApp(App):
self.query_one("#usage-history").border_title = "Usage history"
self.query_one("#constrained-summary").border_title = "Usage history"
self.query_one("#drive-health").border_title = "Drive"
self.query_one("#local-day").border_title = "Local day"
self.query_one("#service-strip").border_title = "Monitoring and actions"
self._refresh_timer = self.set_interval(
self.refresh_interval_s, self.on_refresh_tick
@@ -1277,6 +1303,11 @@ class FenrisTuiApp(App):
self.query_one("#constrained-summary").update(
"[dim]Graph needs ≥80×24[/dim]\n" + message
)
# Hide local-day widget when no data
local_day_widget = self.query_one("#local-day")
local_day_widget.styles.display = "none"
main_grid = self.query_one("#main-grid")
main_grid.remove_class("local-day")
def _render_all_regions(self, conn: sqlite3.Connection, comp: StatusComposition) -> None:
"""Render all four regions from live store data."""
@@ -1355,6 +1386,9 @@ class FenrisTuiApp(App):
)
self.query_one("#drive-health").update(health_text)
# --- Local-day totals (issue #90) ---
self._render_local_day(conn)
def _render_paused_banner(self, comp) -> None:
"""Show the high-contrast Deliberate disable block only when sanctioned.
@@ -1388,6 +1422,46 @@ class FenrisTuiApp(App):
main_grid.remove_class("paused")
main_grid.refresh(layout=True)
def _render_local_day(self, conn: sqlite3.Connection) -> None:
"""Render the local-day activity totals widget (issue #90)."""
try:
from .tz_util import detect_system_tz
from .local_day import query_current_local_day
tz_name = detect_system_tz()
local = query_current_local_day(conn, self._clock_now, tz_name)
except Exception:
local = None
widget = self.query_one("#local-day")
main_grid = self.query_one("#main-grid")
if local is None:
widget.styles.display = "none"
main_grid.remove_class("local-day")
return
main_grid.add_class("local-day")
widget.styles.display = "block"
bw = local["bytes_written"]
br = local["bytes_read"]
partial = "" if local["complete"] else " · totals so far"
tz_display = "%s %s" % (local["tz_name"], local["tz_offset"])
text = (
"[bold]%s[/bold] %s%s\n"
" W %.3f GB · R %.3f GB · %.0f%% coverage"
% (
local["local_date"],
tz_display,
partial,
bw / 1e9,
br / 1e9,
local["coverage"] * 100,
)
)
widget.update(text)
def _on_graph_drill(self, day: str) -> None:
"""Load hourly data when the graph enters drill-down mode."""
graph = self.query_one("#usage-history")