From a290db0491f90df44c0fa775b6bb2218196cd5fd Mon Sep 17 00:00:00 2001 From: Louis King Date: Mon, 8 Dec 2025 19:37:45 +0000 Subject: [PATCH] Updated chart stats --- src/meshcore_hub/api/routes/dashboard.py | 26 ++++---- tests/test_api/test_dashboard.py | 77 ++++++++++++++++++++++-- 2 files changed, 85 insertions(+), 18 deletions(-) diff --git a/src/meshcore_hub/api/routes/dashboard.py b/src/meshcore_hub/api/routes/dashboard.py index c725fab..9393224 100644 --- a/src/meshcore_hub/api/routes/dashboard.py +++ b/src/meshcore_hub/api/routes/dashboard.py @@ -228,15 +228,15 @@ async def get_activity( days: Number of days to include (default 30, max 90) Returns: - Daily advertisement counts for each day in the period + Daily advertisement counts for each day in the period (excluding today) """ # Limit to max 90 days days = min(days, 90) now = datetime.now(timezone.utc) - start_date = (now - timedelta(days=days - 1)).replace( - hour=0, minute=0, second=0, microsecond=0 - ) + # End at start of today (exclude today's incomplete data) + end_date = now.replace(hour=0, minute=0, second=0, microsecond=0) + start_date = end_date - timedelta(days=days) # Query advertisement counts grouped by date # Use SQLite's date() function for grouping (returns string 'YYYY-MM-DD') @@ -248,6 +248,7 @@ async def get_activity( func.count().label("count"), ) .where(Advertisement.received_at >= start_date) + .where(Advertisement.received_at < end_date) .group_by(date_expr) .order_by(date_expr) ) @@ -280,14 +281,14 @@ async def get_message_activity( days: Number of days to include (default 30, max 90) Returns: - Daily message counts for each day in the period + Daily message counts for each day in the period (excluding today) """ days = min(days, 90) now = datetime.now(timezone.utc) - start_date = (now - timedelta(days=days - 1)).replace( - hour=0, minute=0, second=0, microsecond=0 - ) + # End at start of today (exclude today's incomplete data) + end_date = now.replace(hour=0, minute=0, second=0, microsecond=0) + start_date = end_date - timedelta(days=days) # Query message counts grouped by date date_expr = func.date(Message.received_at) @@ -298,6 +299,7 @@ async def get_message_activity( func.count().label("count"), ) .where(Message.received_at >= start_date) + .where(Message.received_at < end_date) .group_by(date_expr) .order_by(date_expr) ) @@ -331,14 +333,14 @@ async def get_node_count_history( days: Number of days to include (default 30, max 90) Returns: - Cumulative node count for each day in the period + Cumulative node count for each day in the period (excluding today) """ days = min(days, 90) now = datetime.now(timezone.utc) - start_date = (now - timedelta(days=days - 1)).replace( - hour=0, minute=0, second=0, microsecond=0 - ) + # End at start of today (exclude today's incomplete data) + end_date = now.replace(hour=0, minute=0, second=0, microsecond=0) + start_date = end_date - timedelta(days=days) # Get all nodes with their creation dates # Count nodes created on or before each date diff --git a/tests/test_api/test_dashboard.py b/tests/test_api/test_dashboard.py index a2616a1..c0a5d67 100644 --- a/tests/test_api/test_dashboard.py +++ b/tests/test_api/test_dashboard.py @@ -1,5 +1,11 @@ """Tests for dashboard API routes.""" +from datetime import datetime, timedelta, timezone + +import pytest + +from meshcore_hub.common.models import Advertisement, Message, Node + class TestDashboardStats: """Tests for GET /dashboard/stats endpoint.""" @@ -63,6 +69,21 @@ class TestDashboardHtml: class TestDashboardActivity: """Tests for GET /dashboard/activity endpoint.""" + @pytest.fixture + def past_advertisement(self, api_db_session): + """Create an advertisement from yesterday (since today is excluded).""" + yesterday = datetime.now(timezone.utc) - timedelta(days=1) + advert = Advertisement( + public_key="abc123def456abc123def456abc123de", + name="TestNode", + adv_type="REPEATER", + received_at=yesterday, + ) + api_db_session.add(advert) + api_db_session.commit() + api_db_session.refresh(advert) + return advert + def test_get_activity_empty(self, client_no_auth): """Test getting activity with empty database.""" response = client_no_auth.get("/api/v1/dashboard/activity") @@ -91,8 +112,12 @@ class TestDashboardActivity: assert data["days"] == 90 assert len(data["data"]) == 90 - def test_get_activity_with_data(self, client_no_auth, sample_advertisement): - """Test getting activity with advertisement in database.""" + def test_get_activity_with_data(self, client_no_auth, past_advertisement): + """Test getting activity with advertisement in database. + + Note: Activity endpoints exclude today's data to avoid showing + incomplete stats early in the day. + """ response = client_no_auth.get("/api/v1/dashboard/activity") assert response.status_code == 200 data = response.json() @@ -104,6 +129,21 @@ class TestDashboardActivity: class TestMessageActivity: """Tests for GET /dashboard/message-activity endpoint.""" + @pytest.fixture + def past_message(self, api_db_session): + """Create a message from yesterday (since today is excluded).""" + yesterday = datetime.now(timezone.utc) - timedelta(days=1) + message = Message( + message_type="direct", + pubkey_prefix="abc123", + text="Hello World", + received_at=yesterday, + ) + api_db_session.add(message) + api_db_session.commit() + api_db_session.refresh(message) + return message + def test_get_message_activity_empty(self, client_no_auth): """Test getting message activity with empty database.""" response = client_no_auth.get("/api/v1/dashboard/message-activity") @@ -132,8 +172,12 @@ class TestMessageActivity: assert data["days"] == 90 assert len(data["data"]) == 90 - def test_get_message_activity_with_data(self, client_no_auth, sample_message): - """Test getting message activity with message in database.""" + def test_get_message_activity_with_data(self, client_no_auth, past_message): + """Test getting message activity with message in database. + + Note: Activity endpoints exclude today's data to avoid showing + incomplete stats early in the day. + """ response = client_no_auth.get("/api/v1/dashboard/message-activity") assert response.status_code == 200 data = response.json() @@ -145,6 +189,23 @@ class TestMessageActivity: class TestNodeCountHistory: """Tests for GET /dashboard/node-count endpoint.""" + @pytest.fixture + def past_node(self, api_db_session): + """Create a node from yesterday (since today is excluded).""" + yesterday = datetime.now(timezone.utc) - timedelta(days=1) + node = Node( + public_key="abc123def456abc123def456abc123de", + name="Test Node", + adv_type="REPEATER", + first_seen=yesterday, + last_seen=yesterday, + created_at=yesterday, + ) + api_db_session.add(node) + api_db_session.commit() + api_db_session.refresh(node) + return node + def test_get_node_count_empty(self, client_no_auth): """Test getting node count with empty database.""" response = client_no_auth.get("/api/v1/dashboard/node-count") @@ -173,8 +234,12 @@ class TestNodeCountHistory: assert data["days"] == 90 assert len(data["data"]) == 90 - def test_get_node_count_with_data(self, client_no_auth, sample_node): - """Test getting node count with node in database.""" + def test_get_node_count_with_data(self, client_no_auth, past_node): + """Test getting node count with node in database. + + Note: Activity endpoints exclude today's data to avoid showing + incomplete stats early in the day. + """ response = client_no_auth.get("/api/v1/dashboard/node-count") assert response.status_code == 200 data = response.json()