From fb0f4c9ab7def730569726dcf61d0529f8adacfa Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sun, 15 Mar 2026 19:48:30 +0000 Subject: [PATCH 1/2] fix: code quality cleanups in report.py and models.py (#66) - Extract _format_session_running_time helper to deduplicate running-time computation in render_live_sessions and _render_active_section - Rename shadowed sessions parameter to filtered in render_cost_view - Standardize _render_totals output-token summation to comprehension form - Simplify AssistantMessageData.toolRequests default_factory to list - Add unit tests for _format_session_running_time Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/copilot_usage/models.py | 4 ++-- src/copilot_usage/report.py | 37 ++++++++++++++++-------------- tests/copilot_usage/test_report.py | 30 ++++++++++++++++++++++++ 3 files changed, 52 insertions(+), 19 deletions(-) diff --git a/src/copilot_usage/models.py b/src/copilot_usage/models.py index 2e14ee8..45498f8 100644 --- a/src/copilot_usage/models.py +++ b/src/copilot_usage/models.py @@ -123,8 +123,8 @@ class AssistantMessageData(BaseModel): interactionId: str = "" reasoningText: str | None = None reasoningOpaque: str | None = None - toolRequests: list[dict[str, object]] = Field( - default_factory=lambda: list[dict[str, object]]() + toolRequests: list[dict[str, object]] = Field( # pyright: ignore[reportUnknownVariableType] + default_factory=list ) diff --git a/src/copilot_usage/report.py b/src/copilot_usage/report.py index 0d55787..2ca8c64 100644 --- a/src/copilot_usage/report.py +++ b/src/copilot_usage/report.py @@ -111,6 +111,18 @@ def _estimated_output_tokens(session: SessionSummary) -> int: return sum(m.usage.outputTokens for m in session.model_metrics.values()) +def _format_session_running_time(session: SessionSummary) -> str: + """Return a human-readable running time for *session*. + + Returns ``"—"`` when the session has no ``start_time``. Otherwise + delegates to :func:`_format_elapsed_since`, preferring + ``last_resume_time`` over ``start_time``. + """ + if not session.start_time: + return "—" + return _format_elapsed_since(session.last_resume_time or session.start_time) + + def render_live_sessions(sessions: list[SessionSummary]) -> None: """Render overview of active sessions only. @@ -144,11 +156,7 @@ def render_live_sessions(sessions: list[SessionSummary]) -> None: short_id = s.session_id[:8] if s.session_id else "—" name = s.name or "—" model = s.model or "—" - running = ( - _format_elapsed_since(s.last_resume_time or s.start_time) - if s.start_time - else "—" - ) + running = _format_session_running_time(s) messages = str(s.user_messages) tokens = f"{_estimated_output_tokens(s):,}" cwd = s.cwd or "—" @@ -580,10 +588,9 @@ def _render_totals(console: Console, sessions: list[SessionSummary]) -> None: total_duration = sum(s.total_api_duration_ms for s in sessions) total_sessions = len(sessions) - total_output = 0 - for s in sessions: - for mm in s.model_metrics.values(): - total_output += mm.usage.outputTokens + total_output = sum( + mm.usage.outputTokens for s in sessions for mm in s.model_metrics.values() + ) pr_label = "premium request" if total_premium == 1 else "premium requests" session_label = "session" if total_sessions == 1 else "sessions" @@ -788,11 +795,7 @@ def _render_active_section( for s in active: name = s.name or s.session_id[:12] model = s.model or "—" - running = ( - _format_elapsed_since(s.last_resume_time or s.start_time) - if s.start_time - else "—" - ) + running = _format_session_running_time(s) table.add_row( name, @@ -847,9 +850,9 @@ def render_cost_view( for premium and the active model calls / output tokens. """ console = target_console or Console() - sessions = _filter_sessions(sessions, since, until) + filtered = _filter_sessions(sessions, since, until) - if not sessions: + if not filtered: console.print("[yellow]No sessions found.[/yellow]") return @@ -866,7 +869,7 @@ def render_cost_view( grand_model_calls = 0 grand_output = 0 - for s in sessions: + for s in filtered: name = s.name or s.session_id[:12] model_calls_display = str(s.model_calls) diff --git a/tests/copilot_usage/test_report.py b/tests/copilot_usage/test_report.py index 7bfa26e..447932d 100644 --- a/tests/copilot_usage/test_report.py +++ b/tests/copilot_usage/test_report.py @@ -27,6 +27,7 @@ _filter_sessions, _format_detail_duration, _format_relative_time, + _format_session_running_time, format_duration, format_tokens, render_cost_view, @@ -1699,3 +1700,32 @@ def test_one_session_one_premium_request(self) -> None: # "session" appears without trailing 's' stripped = output.replace("sessions", "") assert "session" in stripped + + +# --------------------------------------------------------------------------- +# Issue #66 — _format_session_running_time helper +# --------------------------------------------------------------------------- + + +class TestFormatSessionRunningTime: + """Tests for the extracted _format_session_running_time helper.""" + + def test_returns_dash_when_no_start_time(self) -> None: + session = SessionSummary(session_id="no-start") + assert _format_session_running_time(session) == "—" + + def test_uses_start_time_when_no_resume(self) -> None: + start = datetime.now(tz=UTC) - timedelta(minutes=5, seconds=30) + session = SessionSummary(session_id="s1", start_time=start) + result = _format_session_running_time(session) + assert "5m" in result + + def test_uses_last_resume_time_over_start_time(self) -> None: + start = datetime.now(tz=UTC) - timedelta(hours=2) + resume = datetime.now(tz=UTC) - timedelta(minutes=3) + session = SessionSummary( + session_id="s2", start_time=start, last_resume_time=resume + ) + result = _format_session_running_time(session) + assert "3m" in result + assert "2h" not in result From 28b7072510490e35319123af1c4494b607c04274 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sun, 15 Mar 2026 20:05:10 +0000 Subject: [PATCH 2/2] fix: address review comments Remove pyright ignore comment on toolRequests field by using a typed factory (list[dict[str, object]]) instead of bare list. This gives pyright the type information it needs without suppressing diagnostics. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/copilot_usage/models.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/copilot_usage/models.py b/src/copilot_usage/models.py index 45498f8..a8d84ed 100644 --- a/src/copilot_usage/models.py +++ b/src/copilot_usage/models.py @@ -123,8 +123,8 @@ class AssistantMessageData(BaseModel): interactionId: str = "" reasoningText: str | None = None reasoningOpaque: str | None = None - toolRequests: list[dict[str, object]] = Field( # pyright: ignore[reportUnknownVariableType] - default_factory=list + toolRequests: list[dict[str, object]] = Field( + default_factory=list[dict[str, object]] )