Coverage for src/usaspending/models/funding.py: 90%

104 statements  

« prev     ^ index     » next       coverage.py v7.10.6, created at 2025-09-03 17:15 -0700

1"""Funding model for USASpending data.""" 

2 

3from __future__ import annotations 

4 

5from typing import Dict, Any, Optional, TYPE_CHECKING 

6 

7from .base_model import BaseModel 

8from ..utils.formatter import to_float, round_to_millions 

9 

10if TYPE_CHECKING: 

11 from .agency import Agency 

12 

13class Funding(BaseModel): 

14 """Represents federal account funding data for an award.""" 

15 

16 def __init__(self, data: Dict[str, Any]): 

17 """Initialize Funding instance. 

18 

19 Args: 

20 data: Raw funding data from API response 

21 """ 

22 super().__init__(data) 

23 

24 @property 

25 def transaction_obligated_amount(self) -> Optional[float]: 

26 """Amount obligated for this funding record.""" 

27 return to_float(self.get_value("transaction_obligated_amount", default=0.0)) 

28 

29 @property 

30 def gross_outlay_amount(self) -> Optional[float]: 

31 """Gross outlay amount for this funding record.""" 

32 return to_float(self.get_value("gross_outlay_amount", default=0.0)) 

33 

34 @property 

35 def disaster_emergency_fund_code(self) -> Optional[str]: 

36 """Code indicating whether funding is associated with a disaster.""" 

37 return self.get_value("disaster_emergency_fund_code") 

38 

39 @property 

40 def federal_account(self) -> Optional[str]: 

41 """Identifier of the federal account.""" 

42 return self.get_value("federal_account") 

43 

44 @property 

45 def account_title(self) -> Optional[str]: 

46 """Federal account title.""" 

47 return self.get_value("account_title") 

48 

49 @property 

50 def funding_agency_name(self) -> Optional[str]: 

51 """Name of the funding agency.""" 

52 return self.get_value("funding_agency_name") 

53 

54 @property 

55 def funding_agency_id(self) -> Optional[int]: 

56 """Internal surrogate identifier of the funding agency.""" 

57 value = self.get_value("funding_agency_id") 

58 return int(value) if value is not None else None 

59 

60 @property 

61 def funding_toptier_agency_id(self) -> Optional[int]: 

62 """Top-tier funding agency identifier.""" 

63 value = self.get_value("funding_toptier_agency_id") 

64 return int(value) if value is not None else None 

65 

66 @property 

67 def funding_agency_slug(self) -> Optional[str]: 

68 """URL-friendly funding agency identifier.""" 

69 return self.get_value("funding_agency_slug") 

70 

71 @property 

72 def funding_agency(self) -> Optional["Agency"]: 

73 """Retrieve the full Agency object for the funding agency.""" 

74 name = self.funding_agency_name 

75 if name: 

76 return self._client.agencies.find_all_funding_agencies_by_name(name).toptier()[0] 

77 else: 

78 return None 

79 

80 @property 

81 def awarding_agency_name(self) -> Optional[str]: 

82 """Name of the awarding agency.""" 

83 return self.get_value("awarding_agency_name") 

84 

85 @property 

86 def awarding_agency_id(self) -> Optional[int]: 

87 """Internal surrogate identifier of the awarding agency.""" 

88 value = self.get_value("awarding_agency_id") 

89 return int(value) if value is not None else None 

90 

91 @property 

92 def awarding_toptier_agency_id(self) -> Optional[int]: 

93 """Top-tier awarding agency identifier.""" 

94 value = self.get_value("awarding_toptier_agency_id") 

95 return int(value) if value is not None else None 

96 

97 @property 

98 def awarding_agency_slug(self) -> Optional[str]: 

99 """URL-friendly awarding agency identifier.""" 

100 return self.get_value("awarding_agency_slug") 

101 

102 @property 

103 def awarding_agency(self) -> Optional["Agency"]: 

104 """Retrieve the full Agency object for the funding agency.""" 

105 name = self.awarding_agency_name 

106 if name: 

107 return self._client.agencies.find_all_awarding_agencies_by_name(name).toptier()[0] 

108 else: 

109 return None 

110 

111 @property 

112 def object_class(self) -> Optional[str]: 

113 """Object class code.""" 

114 return self.get_value("object_class") 

115 

116 @property 

117 def object_class_name(self) -> Optional[str]: 

118 """Object class name/description.""" 

119 return self.get_value("object_class_name") 

120 

121 @property 

122 def program_activity_code(self) -> Optional[str]: 

123 """Program activity code.""" 

124 return self.get_value("program_activity_code") 

125 

126 @property 

127 def program_activity_name(self) -> Optional[str]: 

128 """Program activity name.""" 

129 return self.get_value("program_activity_name") 

130 

131 @property 

132 def reporting_fiscal_year(self) -> Optional[int]: 

133 """Fiscal year of the submission date.""" 

134 value = self.get_value("reporting_fiscal_year") 

135 return int(value) if value is not None else None 

136 

137 @property 

138 def reporting_fiscal_quarter(self) -> Optional[int]: 

139 """Fiscal quarter of the submission date.""" 

140 value = self.get_value("reporting_fiscal_quarter") 

141 return int(value) if value is not None else None 

142 

143 @property 

144 def reporting_fiscal_month(self) -> Optional[int]: 

145 """Fiscal month of the submission date.""" 

146 value = self.get_value("reporting_fiscal_month") 

147 return int(value) if value is not None else None 

148 

149 @property 

150 def is_quarterly_submission(self) -> Optional[bool]: 

151 """True if submission is quarterly, False if monthly.""" 

152 return self.get_value("is_quarterly_submission") 

153 

154 def __repr__(self) -> str: 

155 """String representation of Funding.""" 

156 year = self.reporting_fiscal_year or "?" 

157 month = self.reporting_fiscal_month 

158 

159 # Format month with zero padding if it's a number 

160 if month is not None: 

161 month_str = f"{month:02d}" 

162 else: 

163 month_str = "?" 

164 

165 if self.transaction_obligated_amount: 

166 amount = self.transaction_obligated_amount 

167 amount_str = f"OBL: {round_to_millions(amount)}" 

168 elif self.gross_outlay_amount: 

169 amount = self.gross_outlay_amount 

170 amount_str = f"OUTLAY: {round_to_millions(amount)}" 

171 else: 

172 amount_str = "?" 

173 

174 agency = self.funding_agency_name or "Unknown Agency" 

175 

176 return f"<Funding {year}-{month_str} {agency} {amount_str}>"