Coverage for src/usaspending/queries/agency_award_summary.py: 100%
38 statements
« prev ^ index » next coverage.py v7.10.6, created at 2025-09-03 17:15 -0700
« prev ^ index » next coverage.py v7.10.6, created at 2025-09-03 17:15 -0700
1"""Agency award summary query implementation for retrieving award aggregations."""
3from typing import TYPE_CHECKING, Optional, Dict, Any, List
4from .single_resource_base import SingleResourceBase
5from ..exceptions import ValidationError
6from ..client import USASpending
7from ..logging_config import USASpendingLogger
9if TYPE_CHECKING:
10 pass
12logger = USASpendingLogger.get_logger(__name__)
15class AgencyAwardSummary(SingleResourceBase):
16 """Retrieve agency award summary data from the USAspending API.
18 This query class handles fetching aggregated award information including
19 transaction counts and obligations filtered by fiscal year, agency type,
20 and award type codes.
21 """
23 def __init__(self, client: USASpending):
24 """Initialize AgencyAwardSummary with client.
26 Args:
27 client: USASpending client instance
28 """
29 super().__init__(client)
30 logger.debug("AgencyAwardSummary initialized with client: %s", client)
32 @property
33 def _endpoint(self) -> str:
34 """Base endpoint for agency award summary retrieval."""
35 return "/agency/"
37 def _construct_endpoint(self, resource_id: str) -> str:
38 """Construct the full endpoint URL for agency award summary.
40 Args:
41 resource_id: The toptier_code for the agency
43 Returns:
44 Full endpoint path including /awards/
45 """
46 return f"{self._endpoint}{resource_id}/awards/"
48 def find_by_id(self, toptier_code: str) -> Dict[str, Any]:
49 """Not used for award summary - use get_awards_summary instead.
51 Raises:
52 NotImplementedError: This method should not be used directly
53 """
54 raise NotImplementedError(
55 "Use get_awards_summary() method instead for award summary data"
56 )
58 def get_awards_summary(
59 self,
60 toptier_code: str,
61 fiscal_year: Optional[int] = None,
62 agency_type: str = "awarding",
63 award_type_codes: Optional[List[str]] = None
64 ) -> Dict[str, Any]:
65 """Retrieve agency award summary with optional filters.
67 Args:
68 toptier_code: The toptier code of an agency (3-4 digit string)
69 fiscal_year: Optional fiscal year for the data (defaults to current)
70 agency_type: "awarding" or "funding" (defaults to "awarding")
71 award_type_codes: Optional list of award type codes to filter by
73 Returns:
74 Dictionary containing:
75 - toptier_code: Agency toptier code
76 - fiscal_year: Fiscal year of data
77 - latest_action_date: Latest transaction date
78 - transaction_count: Number of transactions
79 - obligations: Total obligations amount
80 - messages: Any API messages
82 Raises:
83 ValidationError: If toptier_code is invalid or agency_type is invalid
84 APIError: If API request fails
85 """
86 # Validate toptier_code
87 if not toptier_code:
88 raise ValidationError("toptier_code is required")
90 toptier_code = str(toptier_code).strip()
91 if not toptier_code.isdigit() or len(toptier_code) not in [3, 4]:
92 raise ValidationError(
93 f"Invalid toptier_code: {toptier_code}. "
94 "Must be a 3-4 digit numeric string"
95 )
97 # Validate agency_type
98 if agency_type not in ["awarding", "funding"]:
99 raise ValidationError(
100 f"Invalid agency_type: {agency_type}. "
101 "Must be 'awarding' or 'funding'"
102 )
104 logger.debug(
105 "Fetching award summary for toptier_code: %s, fiscal_year: %s, "
106 "agency_type: %s, award_type_codes: %s",
107 toptier_code, fiscal_year, agency_type, award_type_codes
108 )
110 # Build params
111 params = {"agency_type": agency_type}
113 if fiscal_year is not None:
114 params["fiscal_year"] = fiscal_year
116 if award_type_codes:
117 # Convert to list if needed and filter out None/empty values
118 if isinstance(award_type_codes, (set, frozenset)):
119 award_type_codes = list(award_type_codes)
120 award_type_codes = [code for code in award_type_codes if code]
122 if award_type_codes:
123 # API expects award_type_codes as array parameter
124 params["award_type_codes"] = award_type_codes
126 # Construct endpoint
127 endpoint = self._construct_endpoint(toptier_code)
129 # Make API request with params
130 response = self._client._make_request(
131 "GET",
132 endpoint,
133 params=params
134 )
136 return response