Coverage for src/usaspending/models/grant.py: 97%
60 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"""Grant award model for USASpending data."""
3from __future__ import annotations
4from typing import Dict, Any, Optional, List, TYPE_CHECKING
5from functools import cached_property
7from .award import Award
8from ..utils.formatter import to_float
10if TYPE_CHECKING:
11 from ..queries.subawards_search import SubAwardsSearch
13class Grant(Award):
14 """Grant and assistance award types."""
16 TYPE_FIELDS = [
17 "fain",
18 "uri",
19 "record_type",
20 "cfda_info",
21 "cfda_number",
22 "primary_cfda_info",
23 "sai_number",
24 "funding_opportunity",
25 "non_federal_funding",
26 "total_funding",
27 "transaction_obligated_amount",
28 ]
30 SEARCH_FIELDS = Award.SEARCH_FIELDS + [
31 "Start Date",
32 "End Date",
33 "Award Amount",
34 "Total Outlays",
35 "Award Type",
36 "SAI Number",
37 "CFDA Number",
38 "Assistance Listings",
39 "primary_assistance_listing",
40 ]
42 @property
43 def fain(self) -> Optional[str]:
44 """
45 An identification code assigned to each financial assistance award tracking
46 purposes. The FAIN is tied to that award (and all future modifications to that
47 award) throughout the award's life. Each FAIN is assigned by an agency. Within
48 an agency, FAIN are unique: each new award must be issued a new FAIN. FAIN
49 stands for Federal Award Identification Number, though the digits are letters,
50 not numbers.
51 """
52 return self._lazy_get("fain")
54 @property
55 def uri(self) -> Optional[str]:
56 """The uri of the award"""
57 return self._lazy_get("uri")
59 @property
60 def record_type(self) -> Optional[int]:
61 """Grant record type identifier."""
62 return self._lazy_get("record_type")
64 @property
65 def cfda_info(self) -> List[Dict[str, Any]]:
66 """Catalog of Federal Domestic Assistance information for grants."""
67 return self._lazy_get("cfda_info", "Assistance Listings", default=[])
69 @property
70 def cfda_number(self) -> Optional[str]:
71 """Primary CFDA number for grants."""
72 primary_info = self._lazy_get("primary_cfda_info", "primary_assistance_listing")
73 if primary_info:
74 return primary_info.get("cfda_number")
76 cfda_list = self._lazy_get("cfda_info", "Assistance Listings")
77 if cfda_list:
78 return cfda_list[0].get("cfda_number")
80 return self._lazy_get("cfda_number", "CFDA Number")
82 @property
83 def primary_cfda_info(self) -> Optional[Dict[str, Any]]:
84 """Primary CFDA program information."""
85 return self._lazy_get("primary_cfda_info", "primary_assistance_listing")
87 @property
88 def sai_number(self) -> Optional[str]:
89 """System for Award Identification (SAI) number for grants."""
90 return self._lazy_get("sai_number", "SAI Number")
92 @cached_property
93 def funding_opportunity(self) -> Optional[Dict[str, Any]]:
94 """Funding opportunity details for grants."""
95 return self._lazy_get("funding_opportunity")
97 @property
98 def non_federal_funding(self) -> Optional[float]:
99 """A summation of this award's transactions' non-federal funding amount"""
100 return to_float(self._lazy_get("non_federal_funding", default=None))
102 @property
103 def total_funding(self) -> Optional[float]:
104 """The sum of the federal action obligations and the Non-Federal funding amount."""
105 return to_float(self._lazy_get("total_funding", default=None))
107 @property
108 def transaction_obligated_amount(self) -> Optional[float]:
109 """Transaction-level obligated amount."""
110 return to_float(self._lazy_get("transaction_obligated_amount", default=None))
112 @property
113 def total_subsidy_cost(self) -> Optional[float]:
114 """Total subsidy cost for this award."""
115 return to_float(self._lazy_get("total_subsidy_cost", default=None))
117 @property
118 def base_exercised_options(self) -> Optional[float]:
119 """The total amount obligated for the base and exercised options of this award."""
120 return to_float(self._lazy_get("base_exercised_options", default=None))
122 @property
123 def base_and_all_options(self) -> Optional[float]:
124 """The total amount obligated for the base and all options of this award."""
125 return to_float(self._lazy_get("base_and_all_options", default=None))
126 @property
127 def subawards(self) -> "SubAwardsSearch":
128 """Get subawards query builder for this grant award with appropriate award type filters.
130 Returns a SubAwardsSearch object that can be further filtered and chained.
131 Automatically applies grant award type filters.
133 Examples:
134 >>> grant.subawards.count() # Get count without loading all data
135 >>> grant.subawards.limit(10).all() # Get first 10 subawards
136 >>> list(grant.subawards) # Iterate through all subawards
137 """
138 from ..config import GRANT_CODES
140 # Grant subawards use grant award types only
141 # Note: Due to validation in AwardsSearch, we cannot mix grant/direct_payment/other categories
142 return (self._client.subawards
143 .for_award(self.generated_unique_award_id)
144 .with_award_types(*GRANT_CODES))