usaspending-orm 0.6.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- usaspending/__init__.py +144 -0
- usaspending/cli/__init__.py +2 -0
- usaspending/cli/download_award.py +113 -0
- usaspending/client.py +550 -0
- usaspending/config.py +119 -0
- usaspending/download/__init__.py +8 -0
- usaspending/download/job.py +219 -0
- usaspending/download/manager.py +140 -0
- usaspending/exceptions.py +71 -0
- usaspending/logging_config.py +310 -0
- usaspending/models/__init__.py +86 -0
- usaspending/models/agency.py +631 -0
- usaspending/models/award.py +933 -0
- usaspending/models/award_factory.py +72 -0
- usaspending/models/award_types.py +114 -0
- usaspending/models/base_model.py +214 -0
- usaspending/models/contract.py +147 -0
- usaspending/models/district_spending.py +70 -0
- usaspending/models/download.py +74 -0
- usaspending/models/funding.py +182 -0
- usaspending/models/grant.py +147 -0
- usaspending/models/idv.py +165 -0
- usaspending/models/lazy_record.py +82 -0
- usaspending/models/loan.py +85 -0
- usaspending/models/location.py +125 -0
- usaspending/models/period_of_performance.py +43 -0
- usaspending/models/recipient.py +204 -0
- usaspending/models/recipient_business_categories.py +364 -0
- usaspending/models/recipient_spending.py +58 -0
- usaspending/models/spending.py +70 -0
- usaspending/models/state_spending.py +43 -0
- usaspending/models/subaward.py +270 -0
- usaspending/models/subtier_agency.py +98 -0
- usaspending/models/transaction.py +89 -0
- usaspending/queries/__init__.py +87 -0
- usaspending/queries/agencies_search.py +196 -0
- usaspending/queries/agency_award_summary.py +134 -0
- usaspending/queries/agency_query.py +102 -0
- usaspending/queries/award_query.py +40 -0
- usaspending/queries/awarding_agencies_search.py +16 -0
- usaspending/queries/awards_search.py +602 -0
- usaspending/queries/filters.py +437 -0
- usaspending/queries/funding_agencies_search.py +20 -0
- usaspending/queries/funding_search.py +180 -0
- usaspending/queries/query_builder.py +1430 -0
- usaspending/queries/recipient_query.py +66 -0
- usaspending/queries/recipients_search.py +167 -0
- usaspending/queries/single_resource_base.py +83 -0
- usaspending/queries/spending_search.py +261 -0
- usaspending/queries/sub_agency_query.py +177 -0
- usaspending/queries/subawards_search.py +196 -0
- usaspending/queries/transactions_search.py +213 -0
- usaspending/resources/__init__.py +17 -0
- usaspending/resources/agency_resource.py +105 -0
- usaspending/resources/award_resource.py +119 -0
- usaspending/resources/base_resource.py +31 -0
- usaspending/resources/download_resource.py +102 -0
- usaspending/resources/funding_resource.py +41 -0
- usaspending/resources/recipients_resource.py +104 -0
- usaspending/resources/spending_resource.py +45 -0
- usaspending/resources/subawards_resource.py +68 -0
- usaspending/resources/transactions_resource.py +39 -0
- usaspending/utils/__init__.py +6 -0
- usaspending/utils/formatter.py +550 -0
- usaspending/utils/rate_limit.py +135 -0
- usaspending/utils/retry.py +257 -0
- usaspending/utils/special_cases.yaml +155 -0
- usaspending_orm-0.6.0.dist-info/LICENSE +18 -0
- usaspending_orm-0.6.0.dist-info/METADATA +324 -0
- usaspending_orm-0.6.0.dist-info/RECORD +71 -0
- usaspending_orm-0.6.0.dist-info/WHEEL +4 -0
|
@@ -0,0 +1,364 @@
|
|
|
1
|
+
"""Recipient business category constants and categorizations for USASpending data.
|
|
2
|
+
|
|
3
|
+
This module contains all business category codes, groupings, and descriptions
|
|
4
|
+
as defined by the USASpending.gov API. Categories are organized hierarchically,
|
|
5
|
+
with some categories intentionally belonging to multiple groups.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
# Dictionary of business category groups with their codes and descriptions
|
|
11
|
+
# This mirrors the hierarchical structure used by the USASpending API
|
|
12
|
+
BUSINESS_CATEGORY_GROUPS = {
|
|
13
|
+
"category_business": {
|
|
14
|
+
"category_business": "Category Business",
|
|
15
|
+
"small_business": "Small Business",
|
|
16
|
+
"other_than_small_business": "Not Designated a Small Business",
|
|
17
|
+
"corporate_entity_tax_exempt": "Corporate Entity Tax Exempt",
|
|
18
|
+
"corporate_entity_not_tax_exempt": "Corporate Entity Not Tax Exempt",
|
|
19
|
+
"partnership_or_limited_liability_partnership": "Partnership or Limited Liability Partnership",
|
|
20
|
+
"sole_proprietorship": "Sole Proprietorship",
|
|
21
|
+
"manufacturer_of_goods": "Manufacturer of Goods",
|
|
22
|
+
"subchapter_s_corporation": "Subchapter S Corporation",
|
|
23
|
+
"limited_liability_corporation": "Limited Liability Corporation",
|
|
24
|
+
},
|
|
25
|
+
"minority_owned": {
|
|
26
|
+
"minority_owned_business": "Minority Owned Business",
|
|
27
|
+
"alaskan_native_corporation_owned_firm": "Alaskan Native Corporation Owned Firm",
|
|
28
|
+
"american_indian_owned_business": "American Indian Owned Business",
|
|
29
|
+
"asian_pacific_american_owned_business": "Asian Pacific American Owned Business",
|
|
30
|
+
"black_american_owned_business": "Black American Owned Business",
|
|
31
|
+
"hispanic_american_owned_business": "Hispanic American Owned Business",
|
|
32
|
+
"native_american_owned_business": "Native American Owned Business",
|
|
33
|
+
"native_hawaiian_organization_owned_firm": "Native Hawaiian Organization Owned Firm",
|
|
34
|
+
"subcontinent_asian_indian_american_owned_business": "Indian (Subcontinent) American Owned Business",
|
|
35
|
+
"tribally_owned_firm": "Tribally Owned Firm",
|
|
36
|
+
"other_minority_owned_business": "Other Minority Owned Business",
|
|
37
|
+
},
|
|
38
|
+
"women_owned": {
|
|
39
|
+
"woman_owned_business": "Woman Owned Business",
|
|
40
|
+
"women_owned_small_business": "Women Owned Small Business",
|
|
41
|
+
"economically_disadvantaged_women_owned_small_business": "Economically Disadvantaged Women Owned Small Business",
|
|
42
|
+
"joint_venture_women_owned_small_business": "Joint Venture Women Owned Small Business",
|
|
43
|
+
"joint_venture_economically_disadvantaged_women_owned_small_business": "Joint Venture Economically Disadvantaged Women Owned Small Business",
|
|
44
|
+
},
|
|
45
|
+
"veteran_owned": {
|
|
46
|
+
"veteran_owned_business": "Veteran Owned Business",
|
|
47
|
+
"service_disabled_veteran_owned_business": "Service Disabled Veteran Owned Business",
|
|
48
|
+
},
|
|
49
|
+
"special_designations": {
|
|
50
|
+
"special_designations": "Special Designations",
|
|
51
|
+
"8a_program_participant": "8(a) Program Participant",
|
|
52
|
+
"ability_one_program": "AbilityOne Program Participant",
|
|
53
|
+
"dot_certified_disadvantaged_business_enterprise": "DoT Certified Disadvantaged Business Enterprise",
|
|
54
|
+
"emerging_small_business": "Emerging Small Business",
|
|
55
|
+
"federally_funded_research_and_development_corp": "Federally Funded Research and Development Corp",
|
|
56
|
+
"historically_underutilized_business_firm": "HUBZone Firm",
|
|
57
|
+
"labor_surplus_area_firm": "Labor Surplus Area Firm",
|
|
58
|
+
"sba_certified_8a_joint_venture": "SBA Certified 8 a Joint Venture",
|
|
59
|
+
"self_certified_small_disadvanted_business": "Self-Certified Small Disadvantaged Business",
|
|
60
|
+
"small_agricultural_cooperative": "Small Agricultural Cooperative",
|
|
61
|
+
"small_disadvantaged_business": "Small Disadvantaged Business",
|
|
62
|
+
"community_developed_corporation_owned_firm": "Community Developed Corporation Owned Firm",
|
|
63
|
+
"us_owned_business": "U.S.-Owned Business",
|
|
64
|
+
"foreign_owned_and_us_located_business": "Foreign-Owned and U.S.-Incorporated Business",
|
|
65
|
+
"foreign_owned": "Foreign Owned",
|
|
66
|
+
"foreign_government": "Foreign Government",
|
|
67
|
+
"international_organization": "International Organization",
|
|
68
|
+
"domestic_shelter": "Domestic Shelter",
|
|
69
|
+
"hospital": "Hospital",
|
|
70
|
+
"veterinary_hospital": "Veterinary Hospital",
|
|
71
|
+
},
|
|
72
|
+
"nonprofit": {
|
|
73
|
+
"nonprofit": "Nonprofit Organization",
|
|
74
|
+
"foundation": "Foundation",
|
|
75
|
+
"community_development_corporations": "Community Development Corporation",
|
|
76
|
+
},
|
|
77
|
+
"higher_education": {
|
|
78
|
+
"higher_education": "Higher Education",
|
|
79
|
+
"public_institution_of_higher_education": "Higher Education (Public)",
|
|
80
|
+
"private_institution_of_higher_education": "Higher Education (Private)",
|
|
81
|
+
"minority_serving_institution_of_higher_education": "Higher Education (Minority Serving)",
|
|
82
|
+
"educational_institution": "Educational Institution",
|
|
83
|
+
"school_of_forestry": "School of Forestry",
|
|
84
|
+
"veterinary_college": "Veterinary College",
|
|
85
|
+
},
|
|
86
|
+
"government": {
|
|
87
|
+
"government": "Government",
|
|
88
|
+
"national_government": "U.S. National Government",
|
|
89
|
+
"regional_and_state_government": "U.S. Regional/State Government",
|
|
90
|
+
"regional_organization": "U.S. Regional Government Organization",
|
|
91
|
+
"interstate_entity": "U.S. Interstate Government Entity",
|
|
92
|
+
"us_territory_or_possession": "U.S. Territory Government",
|
|
93
|
+
"local_government": "U.S. Local Government",
|
|
94
|
+
"indian_native_american_tribal_government": "Native American Tribal Government",
|
|
95
|
+
"authorities_and_commissions": "U.S. Government Authorities",
|
|
96
|
+
"council_of_governments": "Council of Governments",
|
|
97
|
+
},
|
|
98
|
+
"individuals": {
|
|
99
|
+
"individuals": "Individuals",
|
|
100
|
+
},
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
# Categories that intentionally appear in multiple groups
|
|
104
|
+
# Based on USASpending API logic
|
|
105
|
+
OVERLAPPING_CATEGORIES = {
|
|
106
|
+
# These appear in both category_business and special_designations
|
|
107
|
+
"emerging_small_business": ["category_business", "special_designations"],
|
|
108
|
+
"small_agricultural_cooperative": ["category_business", "special_designations"],
|
|
109
|
+
"small_disadvantaged_business": ["category_business", "special_designations"],
|
|
110
|
+
"self_certified_small_disadvanted_business": [
|
|
111
|
+
"category_business",
|
|
112
|
+
"special_designations",
|
|
113
|
+
],
|
|
114
|
+
# Women-owned small businesses also trigger small_business
|
|
115
|
+
"women_owned_small_business": ["women_owned", "category_business"],
|
|
116
|
+
"economically_disadvantaged_women_owned_small_business": [
|
|
117
|
+
"women_owned",
|
|
118
|
+
"category_business",
|
|
119
|
+
],
|
|
120
|
+
"joint_venture_women_owned_small_business": ["women_owned", "category_business"],
|
|
121
|
+
"joint_venture_economically_disadvantaged_women_owned_small_business": [
|
|
122
|
+
"women_owned",
|
|
123
|
+
"category_business",
|
|
124
|
+
],
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
# Create a complete mapping including overlaps
|
|
129
|
+
def _build_complete_descriptions():
|
|
130
|
+
"""Build complete description map including overlapping categories."""
|
|
131
|
+
descriptions = {}
|
|
132
|
+
for group_name, group_dict in BUSINESS_CATEGORY_GROUPS.items():
|
|
133
|
+
for code, description in group_dict.items():
|
|
134
|
+
if code not in descriptions:
|
|
135
|
+
descriptions[code] = description
|
|
136
|
+
return descriptions
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
BUSINESS_CATEGORY_DESCRIPTIONS = _build_complete_descriptions()
|
|
140
|
+
|
|
141
|
+
# Create frozensets for each group
|
|
142
|
+
CATEGORY_BUSINESS_CODES = frozenset(
|
|
143
|
+
BUSINESS_CATEGORY_GROUPS["category_business"].keys()
|
|
144
|
+
)
|
|
145
|
+
MINORITY_OWNED_CODES = frozenset(BUSINESS_CATEGORY_GROUPS["minority_owned"].keys())
|
|
146
|
+
WOMEN_OWNED_CODES = frozenset(BUSINESS_CATEGORY_GROUPS["women_owned"].keys())
|
|
147
|
+
VETERAN_OWNED_CODES = frozenset(BUSINESS_CATEGORY_GROUPS["veteran_owned"].keys())
|
|
148
|
+
SPECIAL_DESIGNATIONS_CODES = frozenset(
|
|
149
|
+
BUSINESS_CATEGORY_GROUPS["special_designations"].keys()
|
|
150
|
+
)
|
|
151
|
+
NONPROFIT_CODES = frozenset(BUSINESS_CATEGORY_GROUPS["nonprofit"].keys())
|
|
152
|
+
HIGHER_EDUCATION_CODES = frozenset(BUSINESS_CATEGORY_GROUPS["higher_education"].keys())
|
|
153
|
+
GOVERNMENT_CODES = frozenset(BUSINESS_CATEGORY_GROUPS["government"].keys())
|
|
154
|
+
INDIVIDUALS_CODES = frozenset(BUSINESS_CATEGORY_GROUPS["individuals"].keys())
|
|
155
|
+
|
|
156
|
+
# All valid business category codes
|
|
157
|
+
ALL_BUSINESS_CATEGORIES = frozenset(BUSINESS_CATEGORY_DESCRIPTIONS.keys())
|
|
158
|
+
|
|
159
|
+
# For backward compatibility - matches original frozenset structure
|
|
160
|
+
BUSINESS_CATEGORIES = ALL_BUSINESS_CATEGORIES
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
def get_category_group(code: str) -> str:
|
|
164
|
+
"""Get the primary category group name for a given business category code.
|
|
165
|
+
|
|
166
|
+
For categories that belong to multiple groups, returns the first/primary group.
|
|
167
|
+
Use get_all_groups_for_code() to get all groups.
|
|
168
|
+
|
|
169
|
+
Args:
|
|
170
|
+
code: Business category code (e.g., "small_business", "nonprofit")
|
|
171
|
+
|
|
172
|
+
Returns:
|
|
173
|
+
Primary category group name (e.g., "category_business", "nonprofit") or empty string if not found
|
|
174
|
+
"""
|
|
175
|
+
if not isinstance(code, str):
|
|
176
|
+
return ""
|
|
177
|
+
|
|
178
|
+
# Check overlapping categories first for their primary group
|
|
179
|
+
if code in OVERLAPPING_CATEGORIES:
|
|
180
|
+
return OVERLAPPING_CATEGORIES[code][0]
|
|
181
|
+
|
|
182
|
+
# Check each group
|
|
183
|
+
for group_name, codes in BUSINESS_CATEGORY_GROUPS.items():
|
|
184
|
+
if code in codes:
|
|
185
|
+
return group_name
|
|
186
|
+
return ""
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
def get_all_groups_for_code(code: str) -> list[str]:
|
|
190
|
+
"""Get all category groups that a business category code belongs to.
|
|
191
|
+
|
|
192
|
+
Handles categories that belong to multiple groups (e.g., emerging_small_business).
|
|
193
|
+
|
|
194
|
+
Args:
|
|
195
|
+
code: Business category code
|
|
196
|
+
|
|
197
|
+
Returns:
|
|
198
|
+
List of all category group names the code belongs to
|
|
199
|
+
"""
|
|
200
|
+
if not isinstance(code, str):
|
|
201
|
+
return []
|
|
202
|
+
|
|
203
|
+
groups = []
|
|
204
|
+
|
|
205
|
+
# Check if it's an overlapping category
|
|
206
|
+
if code in OVERLAPPING_CATEGORIES:
|
|
207
|
+
return OVERLAPPING_CATEGORIES[code].copy()
|
|
208
|
+
|
|
209
|
+
# Otherwise check each group
|
|
210
|
+
for group_name, codes in BUSINESS_CATEGORY_GROUPS.items():
|
|
211
|
+
if code in codes:
|
|
212
|
+
groups.append(group_name)
|
|
213
|
+
|
|
214
|
+
return groups
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
def is_valid_business_category(code: str) -> bool:
|
|
218
|
+
"""Check if a code is a valid business category.
|
|
219
|
+
|
|
220
|
+
Args:
|
|
221
|
+
code: Business category code to validate
|
|
222
|
+
|
|
223
|
+
Returns:
|
|
224
|
+
True if the code is valid, False otherwise
|
|
225
|
+
"""
|
|
226
|
+
if not isinstance(code, str):
|
|
227
|
+
return False
|
|
228
|
+
return code in ALL_BUSINESS_CATEGORIES
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
def get_description(code: str) -> str:
|
|
232
|
+
"""Get the description for a given business category code.
|
|
233
|
+
|
|
234
|
+
Args:
|
|
235
|
+
code: Business category code
|
|
236
|
+
|
|
237
|
+
Returns:
|
|
238
|
+
Description string or empty string if not found
|
|
239
|
+
"""
|
|
240
|
+
if not isinstance(code, str):
|
|
241
|
+
return ""
|
|
242
|
+
return BUSINESS_CATEGORY_DESCRIPTIONS.get(code, "")
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
# Convenience functions for common category checks
|
|
246
|
+
def is_small_business(code: str) -> bool:
|
|
247
|
+
"""Check if a code represents a small business.
|
|
248
|
+
|
|
249
|
+
Args:
|
|
250
|
+
code: Business category code
|
|
251
|
+
|
|
252
|
+
Returns:
|
|
253
|
+
True if the code represents a small business
|
|
254
|
+
"""
|
|
255
|
+
if not isinstance(code, str):
|
|
256
|
+
return False
|
|
257
|
+
|
|
258
|
+
# Direct small business code
|
|
259
|
+
if code == "small_business":
|
|
260
|
+
return True
|
|
261
|
+
|
|
262
|
+
# Women-owned small businesses
|
|
263
|
+
if code in {
|
|
264
|
+
"women_owned_small_business",
|
|
265
|
+
"economically_disadvantaged_women_owned_small_business",
|
|
266
|
+
"joint_venture_women_owned_small_business",
|
|
267
|
+
"joint_venture_economically_disadvantaged_women_owned_small_business",
|
|
268
|
+
}:
|
|
269
|
+
return True
|
|
270
|
+
|
|
271
|
+
# Special designation small businesses
|
|
272
|
+
if code in {
|
|
273
|
+
"emerging_small_business",
|
|
274
|
+
"self_certified_small_disadvanted_business",
|
|
275
|
+
"small_agricultural_cooperative",
|
|
276
|
+
"small_disadvantaged_business",
|
|
277
|
+
}:
|
|
278
|
+
return True
|
|
279
|
+
|
|
280
|
+
return False
|
|
281
|
+
|
|
282
|
+
|
|
283
|
+
def is_minority_owned(code: str) -> bool:
|
|
284
|
+
"""Check if a code represents a minority-owned business.
|
|
285
|
+
|
|
286
|
+
Args:
|
|
287
|
+
code: Business category code
|
|
288
|
+
|
|
289
|
+
Returns:
|
|
290
|
+
True if the code represents a minority-owned business
|
|
291
|
+
"""
|
|
292
|
+
if not isinstance(code, str):
|
|
293
|
+
return False
|
|
294
|
+
return code in MINORITY_OWNED_CODES
|
|
295
|
+
|
|
296
|
+
|
|
297
|
+
def is_women_owned(code: str) -> bool:
|
|
298
|
+
"""Check if a code represents a women-owned business.
|
|
299
|
+
|
|
300
|
+
Args:
|
|
301
|
+
code: Business category code
|
|
302
|
+
|
|
303
|
+
Returns:
|
|
304
|
+
True if the code represents a women-owned business
|
|
305
|
+
"""
|
|
306
|
+
if not isinstance(code, str):
|
|
307
|
+
return False
|
|
308
|
+
return code in WOMEN_OWNED_CODES
|
|
309
|
+
|
|
310
|
+
|
|
311
|
+
def is_veteran_owned(code: str) -> bool:
|
|
312
|
+
"""Check if a code represents a veteran-owned business.
|
|
313
|
+
|
|
314
|
+
Args:
|
|
315
|
+
code: Business category code
|
|
316
|
+
|
|
317
|
+
Returns:
|
|
318
|
+
True if the code represents a veteran-owned business
|
|
319
|
+
"""
|
|
320
|
+
if not isinstance(code, str):
|
|
321
|
+
return False
|
|
322
|
+
return code in VETERAN_OWNED_CODES
|
|
323
|
+
|
|
324
|
+
|
|
325
|
+
def is_government_entity(code: str) -> bool:
|
|
326
|
+
"""Check if a code represents a government entity.
|
|
327
|
+
|
|
328
|
+
Args:
|
|
329
|
+
code: Business category code
|
|
330
|
+
|
|
331
|
+
Returns:
|
|
332
|
+
True if the code represents a government entity
|
|
333
|
+
"""
|
|
334
|
+
if not isinstance(code, str):
|
|
335
|
+
return False
|
|
336
|
+
return code in GOVERNMENT_CODES
|
|
337
|
+
|
|
338
|
+
|
|
339
|
+
def is_educational_institution(code: str) -> bool:
|
|
340
|
+
"""Check if a code represents an educational institution.
|
|
341
|
+
|
|
342
|
+
Args:
|
|
343
|
+
code: Business category code
|
|
344
|
+
|
|
345
|
+
Returns:
|
|
346
|
+
True if the code represents an educational institution
|
|
347
|
+
"""
|
|
348
|
+
if not isinstance(code, str):
|
|
349
|
+
return False
|
|
350
|
+
return code in HIGHER_EDUCATION_CODES
|
|
351
|
+
|
|
352
|
+
|
|
353
|
+
def is_nonprofit_organization(code: str) -> bool:
|
|
354
|
+
"""Check if a code represents a nonprofit organization.
|
|
355
|
+
|
|
356
|
+
Args:
|
|
357
|
+
code: Business category code
|
|
358
|
+
|
|
359
|
+
Returns:
|
|
360
|
+
True if the code represents a nonprofit organization
|
|
361
|
+
"""
|
|
362
|
+
if not isinstance(code, str):
|
|
363
|
+
return False
|
|
364
|
+
return code in NONPROFIT_CODES
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
"""Recipient spending model for USASpending spending by recipient data."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Optional, TYPE_CHECKING
|
|
6
|
+
from decimal import Decimal
|
|
7
|
+
from ..utils.formatter import to_decimal, round_to_millions
|
|
8
|
+
from .recipient import Recipient
|
|
9
|
+
|
|
10
|
+
if TYPE_CHECKING:
|
|
11
|
+
from ..client import USASpendingClient
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class RecipientSpending(Recipient):
|
|
15
|
+
"""Model for spending by recipient data.
|
|
16
|
+
|
|
17
|
+
Represents spending data grouped by recipient with recipient-specific
|
|
18
|
+
fields like recipient_id and UEI.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
def __init__(self, data: dict, client: Optional["USASpendingClient"] = None):
|
|
22
|
+
"""Initialize RecipientSpending model.
|
|
23
|
+
|
|
24
|
+
Args:
|
|
25
|
+
data: Raw recipient spending data from API
|
|
26
|
+
client: USASpendingClient client instance
|
|
27
|
+
"""
|
|
28
|
+
super().__init__(data, client)
|
|
29
|
+
|
|
30
|
+
@property
|
|
31
|
+
def duns(self) -> Optional[str]:
|
|
32
|
+
return self.code
|
|
33
|
+
|
|
34
|
+
@property
|
|
35
|
+
def code(self) -> Optional[str]:
|
|
36
|
+
"""DUNS number from spending data (stored in 'code' field)."""
|
|
37
|
+
return self.get_value(["code"], default=None)
|
|
38
|
+
|
|
39
|
+
@property
|
|
40
|
+
def amount(self) -> Optional[Decimal]:
|
|
41
|
+
"""Total spending amount for this record."""
|
|
42
|
+
return to_decimal(self.get_value(["amount"]))
|
|
43
|
+
|
|
44
|
+
@property
|
|
45
|
+
def total_outlays(self) -> Optional[Decimal]:
|
|
46
|
+
"""Total outlays for this spending record."""
|
|
47
|
+
return to_decimal(self.get_value(["total_outlays"]))
|
|
48
|
+
|
|
49
|
+
@property
|
|
50
|
+
def spending_level(self) -> Optional[str]:
|
|
51
|
+
"""The spending level used for this data (transactions, awards, subawards)."""
|
|
52
|
+
return self.get_value(["spending_level"])
|
|
53
|
+
|
|
54
|
+
def __repr__(self) -> str:
|
|
55
|
+
"""String representation of RecipientSpending."""
|
|
56
|
+
name = self.name or "Unknown Recipient"
|
|
57
|
+
formatted_amount = round_to_millions(self.amount) or 0
|
|
58
|
+
return f"<RecipientSpending {name}: {formatted_amount}>"
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
"""Base model for USASpending spending by category data."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Optional, TYPE_CHECKING
|
|
6
|
+
from decimal import Decimal
|
|
7
|
+
from ..utils.formatter import to_decimal
|
|
8
|
+
from .base_model import BaseModel
|
|
9
|
+
|
|
10
|
+
if TYPE_CHECKING:
|
|
11
|
+
from ..client import USASpendingClient
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class Spending(BaseModel):
|
|
15
|
+
"""Base model for spending by category data.
|
|
16
|
+
|
|
17
|
+
Represents common fields across spending by recipient and district categories.
|
|
18
|
+
This model provides access to spending data with amounts, names, codes, and outlays.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
def __init__(self, data: dict, client: Optional["USASpendingClient"] = None):
|
|
22
|
+
"""Initialize Spending model.
|
|
23
|
+
|
|
24
|
+
Args:
|
|
25
|
+
data: Raw spending data from API
|
|
26
|
+
client: USASpendingClient client instance
|
|
27
|
+
"""
|
|
28
|
+
super().__init__(data)
|
|
29
|
+
self._client = client
|
|
30
|
+
|
|
31
|
+
@property
|
|
32
|
+
def id(self) -> Optional[int]:
|
|
33
|
+
"""Database ID for the spending record."""
|
|
34
|
+
return self.get_value(["id"])
|
|
35
|
+
|
|
36
|
+
@property
|
|
37
|
+
def name(self) -> Optional[str]:
|
|
38
|
+
"""Display name for the spending category (recipient name or district name)."""
|
|
39
|
+
return self.get_value(["name"])
|
|
40
|
+
|
|
41
|
+
@property
|
|
42
|
+
def code(self) -> Optional[str]:
|
|
43
|
+
"""Code associated with the spending record (DUNS, district code, etc.)."""
|
|
44
|
+
return self.get_value(["code"])
|
|
45
|
+
|
|
46
|
+
@property
|
|
47
|
+
def amount(self) -> Optional[Decimal]:
|
|
48
|
+
"""Total spending amount for this record."""
|
|
49
|
+
return to_decimal(self.get_value(["amount"]))
|
|
50
|
+
|
|
51
|
+
@property
|
|
52
|
+
def total_outlays(self) -> Optional[Decimal]:
|
|
53
|
+
"""Total outlays for this spending record."""
|
|
54
|
+
return to_decimal(self.get_value(["total_outlays"]))
|
|
55
|
+
|
|
56
|
+
@property
|
|
57
|
+
def spending_level(self) -> Optional[str]:
|
|
58
|
+
"""The spending level used for this data (transactions, awards, subawards)."""
|
|
59
|
+
return self.get_value(["spending_level"])
|
|
60
|
+
|
|
61
|
+
@property
|
|
62
|
+
def category(self) -> Optional[str]:
|
|
63
|
+
"""The category type (recipient or district)."""
|
|
64
|
+
return self.get_value(["category"])
|
|
65
|
+
|
|
66
|
+
def __repr__(self) -> str:
|
|
67
|
+
"""String representation of Spending."""
|
|
68
|
+
name = self.name or "Unknown"
|
|
69
|
+
amount = self.amount or 0
|
|
70
|
+
return f"<Spending {name}: ${amount:,.2f}>"
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
"""State spending model for USASpending spending by state/territory data."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Optional, TYPE_CHECKING
|
|
6
|
+
|
|
7
|
+
from .spending import Spending
|
|
8
|
+
|
|
9
|
+
if TYPE_CHECKING:
|
|
10
|
+
from ..client import USASpendingClient
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class StateSpending(Spending):
|
|
14
|
+
"""Model for spending by state/territory data.
|
|
15
|
+
|
|
16
|
+
Represents spending data grouped by state/territory with
|
|
17
|
+
state-specific properties.
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
def __init__(self, data: dict, client: Optional["USASpendingClient"] = None):
|
|
21
|
+
"""Initialize StateSpending model.
|
|
22
|
+
|
|
23
|
+
Args:
|
|
24
|
+
data: Raw state spending data from API
|
|
25
|
+
client: USASpendingClient client instance
|
|
26
|
+
"""
|
|
27
|
+
super().__init__(data, client)
|
|
28
|
+
|
|
29
|
+
@property
|
|
30
|
+
def state_code(self) -> Optional[str]:
|
|
31
|
+
"""State/territory code (e.g., 'WA', 'CA')."""
|
|
32
|
+
return self.code
|
|
33
|
+
|
|
34
|
+
@property
|
|
35
|
+
def state_name(self) -> Optional[str]:
|
|
36
|
+
"""Full state/territory name (e.g., 'Washington')."""
|
|
37
|
+
return self.name
|
|
38
|
+
|
|
39
|
+
def __repr__(self) -> str:
|
|
40
|
+
"""String representation of StateSpending."""
|
|
41
|
+
name = self.state_name or "Unknown State"
|
|
42
|
+
amount = self.amount or 0
|
|
43
|
+
return f"<StateSpending {name}: ${amount:,.2f}>"
|