diff --git a/collector_db/AsyncDatabaseClient.py b/collector_db/AsyncDatabaseClient.py index 03c652c9..ac6216d6 100644 --- a/collector_db/AsyncDatabaseClient.py +++ b/collector_db/AsyncDatabaseClient.py @@ -2001,14 +2001,14 @@ async def get_urls_breakdown_submitted_metrics( ) -> GetMetricsURLsBreakdownSubmittedResponseDTO: # Build the query - week = func.date_trunc('week', URLDataSource.created_at) + month = func.date_trunc('month', URLDataSource.created_at) query = ( select( - week.label('week'), + month.label('month'), func.count(URLDataSource.id).label('count_submitted'), ) - .group_by(week) - .order_by(week.asc()) + .group_by(month) + .order_by(month.asc()) ) # Execute the query @@ -2017,7 +2017,7 @@ async def get_urls_breakdown_submitted_metrics( final_results: list[GetMetricsURLsBreakdownSubmittedInnerDTO] = [] for result in results: dto = GetMetricsURLsBreakdownSubmittedInnerDTO( - week_of=result.week, + month=result.month.strftime("%B %Y"), count_submitted=result.count_submitted ) final_results.append(dto) @@ -2111,12 +2111,12 @@ async def get_urls_breakdown_pending_metrics( ).cte("flags") - week = func.date_trunc('week', URL.created_at) + month = func.date_trunc('month', URL.created_at) # Build the query query = ( select( - week.label('week'), + month.label('month'), func.count(URL.id).label('count_total'), func.count(case( (flags.c.has_user_record_type_annotation == True, 1)) @@ -2130,8 +2130,8 @@ async def get_urls_breakdown_pending_metrics( ) .outerjoin(flags, flags.c.url_id == URL.id) .where(URL.outcome == URLStatus.PENDING.value) - .group_by(week) - .order_by(week.asc()) + .group_by(month) + .order_by(month.asc()) ) # Execute the query and return the results @@ -2141,7 +2141,7 @@ async def get_urls_breakdown_pending_metrics( for result in all_results: dto = GetMetricsURLsBreakdownPendingResponseInnerDTO( - week_created_at=result.week, + month=result.month.strftime("%B %Y"), count_pending_total=result.count_total, count_pending_relevant_user=result.user_relevant_count, count_pending_record_type_user=result.user_record_type_count, @@ -2157,16 +2157,18 @@ async def get_backlog_metrics( self, session: AsyncSession ) -> GetMetricsBacklogResponseDTO: - # 1. Create a subquery that assigns row_number() partitioned by week - weekly_snapshots_subq = ( + month = func.date_trunc('month', BacklogSnapshot.created_at) + + # 1. Create a subquery that assigns row_number() partitioned by month + monthly_snapshot_subq = ( select( BacklogSnapshot.id, BacklogSnapshot.created_at, BacklogSnapshot.count_pending_total, - func.date_trunc('week', BacklogSnapshot.created_at).label("week_start"), + month.label("month_start"), func.row_number() .over( - partition_by=func.date_trunc('week', BacklogSnapshot.created_at), + partition_by=month, order_by=BacklogSnapshot.created_at.desc() ) .label("row_number") @@ -2174,15 +2176,15 @@ async def get_backlog_metrics( .subquery() ) - # 2. Filter for the top (most recent) row in each week + # 2. Filter for the top (most recent) row in each month stmt = ( select( - weekly_snapshots_subq.c.week_start, - weekly_snapshots_subq.c.created_at, - weekly_snapshots_subq.c.count_pending_total + monthly_snapshot_subq.c.month_start, + monthly_snapshot_subq.c.created_at, + monthly_snapshot_subq.c.count_pending_total ) - .where(weekly_snapshots_subq.c.row_number == 1) - .order_by(weekly_snapshots_subq.c.week_start) + .where(monthly_snapshot_subq.c.row_number == 1) + .order_by(monthly_snapshot_subq.c.month_start) ) raw_result = await session.execute(stmt) @@ -2191,7 +2193,7 @@ async def get_backlog_metrics( for result in results: final_results.append( GetMetricsBacklogResponseInnerDTO( - week_of=result.week_start, + month=result.month_start.strftime("%B %Y"), count_pending_total=result.count_pending_total, ) ) diff --git a/core/DTOs/GetMetricsBacklogResponse.py b/core/DTOs/GetMetricsBacklogResponse.py index 0df38324..8193e385 100644 --- a/core/DTOs/GetMetricsBacklogResponse.py +++ b/core/DTOs/GetMetricsBacklogResponse.py @@ -1,10 +1,21 @@ -import datetime +from datetime import datetime + +from pydantic import BaseModel, field_validator -from pydantic import BaseModel class GetMetricsBacklogResponseInnerDTO(BaseModel): - week_of: datetime.date + month: str count_pending_total: int + @field_validator("month") + @classmethod + def validate_month_format(cls, v: str) -> str: + try: + # This will raise ValueError if format doesn't match + datetime.strptime(v, "%B %Y") + except ValueError: + raise ValueError("month must be in the format 'MonthName YYYY' (e.g., 'May 2025')") + return v + class GetMetricsBacklogResponseDTO(BaseModel): entries: list[GetMetricsBacklogResponseInnerDTO] \ No newline at end of file diff --git a/core/DTOs/GetMetricsURLsBreakdownPendingResponseDTO.py b/core/DTOs/GetMetricsURLsBreakdownPendingResponseDTO.py index 22235e45..16e596d5 100644 --- a/core/DTOs/GetMetricsURLsBreakdownPendingResponseDTO.py +++ b/core/DTOs/GetMetricsURLsBreakdownPendingResponseDTO.py @@ -1,12 +1,22 @@ -from pydantic import BaseModel +from pydantic import BaseModel, field_validator from datetime import datetime class GetMetricsURLsBreakdownPendingResponseInnerDTO(BaseModel): - week_created_at: datetime + month: str count_pending_total: int count_pending_relevant_user: int count_pending_record_type_user: int count_pending_agency_user: int + @field_validator("month") + @classmethod + def validate_month_format(cls, v: str) -> str: + try: + # This will raise ValueError if format doesn't match + datetime.strptime(v, "%B %Y") + except ValueError: + raise ValueError("month must be in the format 'MonthName YYYY' (e.g., 'May 2025')") + return v + class GetMetricsURLsBreakdownPendingResponseDTO(BaseModel): entries: list[GetMetricsURLsBreakdownPendingResponseInnerDTO] \ No newline at end of file diff --git a/core/DTOs/GetMetricsURLsBreakdownSubmittedResponseDTO.py b/core/DTOs/GetMetricsURLsBreakdownSubmittedResponseDTO.py index d5c1dde5..2ac4e768 100644 --- a/core/DTOs/GetMetricsURLsBreakdownSubmittedResponseDTO.py +++ b/core/DTOs/GetMetricsURLsBreakdownSubmittedResponseDTO.py @@ -1,10 +1,21 @@ -from datetime import date +from datetime import datetime + +from pydantic import BaseModel, field_validator -from pydantic import BaseModel class GetMetricsURLsBreakdownSubmittedInnerDTO(BaseModel): - week_of: date + month: str count_submitted: int + @field_validator("month") + @classmethod + def validate_month_format(cls, v: str) -> str: + try: + # This will raise ValueError if format doesn't match + datetime.strptime(v, "%B %Y") + except ValueError: + raise ValueError("month must be in the format 'MonthName YYYY' (e.g., 'May 2025')") + return v + class GetMetricsURLsBreakdownSubmittedResponseDTO(BaseModel): entries: list[GetMetricsURLsBreakdownSubmittedInnerDTO] \ No newline at end of file diff --git a/tests/test_automated/integration/api/test_metrics.py b/tests/test_automated/integration/api/test_metrics.py index fc45ad0b..b8eb6ca6 100644 --- a/tests/test_automated/integration/api/test_metrics.py +++ b/tests/test_automated/integration/api/test_metrics.py @@ -76,8 +76,8 @@ async def test_get_batches_aggregated_metrics(api_test_helper): @pytest.mark.asyncio async def test_get_batches_breakdown_metrics(api_test_helper): - # Create a different batch for each week, with different URLs - today = pendulum.today() + # Create a different batch for each month, with different URLs + today = pendulum.parse('2021-01-01') ath = api_test_helper batch_1_params = TestBatchCreationParameters( @@ -169,7 +169,7 @@ async def test_get_batches_breakdown_metrics(api_test_helper): async def test_get_urls_breakdown_submitted_metrics(api_test_helper): # Create URLs with submitted status, broken down in different amounts by different weeks # And ensure the URLs are - today = pendulum.today() + today = pendulum.parse('2021-01-01') ath = api_test_helper batch_1_params = TestBatchCreationParameters( @@ -234,7 +234,7 @@ async def test_get_urls_breakdown_pending_metrics(api_test_helper): # with a different number of kinds of annotations per URLs - today = pendulum.today() + today = pendulum.parse('2021-01-01') ath = api_test_helper agency_id = await ath.db_data_creator.agency() @@ -315,7 +315,7 @@ async def test_get_urls_breakdown_pending_metrics(api_test_helper): @pytest.mark.asyncio async def test_get_urls_aggregate_metrics(api_test_helper): ath = api_test_helper - today = pendulum.today() + today = pendulum.parse('2021-01-01') batch_0_params = TestBatchCreationParameters( strategy=CollectorType.MANUAL, @@ -384,14 +384,14 @@ async def test_get_urls_aggregate_metrics(api_test_helper): @pytest.mark.asyncio async def test_get_backlog_metrics(api_test_helper): - today = pendulum.today() + today = pendulum.parse('2021-01-01') ath = api_test_helper adb_client = ath.adb_client() - # Populate the backlog table and test that backlog metrics returned on a weekly basis - # Ensure that multiple days in each week are added to the backlog table, with different values + # Populate the backlog table and test that backlog metrics returned on a monthly basis + # Ensure that multiple days in each month are added to the backlog table, with different values batch_1_params = TestBatchCreationParameters( @@ -413,11 +413,11 @@ async def test_get_backlog_metrics(api_test_helper): batch_1 = await ath.db_data_creator.batch_v2(batch_1_params) await adb_client.populate_backlog_snapshot( - dt=today.subtract(weeks=3).naive() + dt=today.subtract(months=3).naive() ) await adb_client.populate_backlog_snapshot( - dt=today.subtract(weeks=2, days=3).naive() + dt=today.subtract(months=2, days=3).naive() ) batch_2_params = TestBatchCreationParameters( @@ -439,11 +439,11 @@ async def test_get_backlog_metrics(api_test_helper): batch_2 = await ath.db_data_creator.batch_v2(batch_2_params) await adb_client.populate_backlog_snapshot( - dt=today.subtract(weeks=2).naive() + dt=today.subtract(months=2).naive() ) await adb_client.populate_backlog_snapshot( - dt=today.subtract(weeks=1, days=4).naive() + dt=today.subtract(months=1, days=4).naive() ) batch_3_params = TestBatchCreationParameters( @@ -465,14 +465,14 @@ async def test_get_backlog_metrics(api_test_helper): batch_3 = await ath.db_data_creator.batch_v2(batch_3_params) await adb_client.populate_backlog_snapshot( - dt=today.subtract(weeks=1).naive() + dt=today.subtract(months=1).naive() ) dto = await ath.request_validator.get_backlog_metrics() assert len(dto.entries) == 3 - # Test that the count closest to the beginning of the week is returned for each week + # Test that the count closest to the beginning of the month is returned for each month assert dto.entries[0].count_pending_total == 1 assert dto.entries[1].count_pending_total == 5 assert dto.entries[2].count_pending_total == 12 \ No newline at end of file