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
« prev ^ index » next coverage.py v7.10.6, created at 2025-09-03 17:15 -0700
1"""Funding model for USASpending data."""
3from __future__ import annotations
5from typing import Dict, Any, Optional, TYPE_CHECKING
7from .base_model import BaseModel
8from ..utils.formatter import to_float, round_to_millions
10if TYPE_CHECKING:
11 from .agency import Agency
13class Funding(BaseModel):
14 """Represents federal account funding data for an award."""
16 def __init__(self, data: Dict[str, Any]):
17 """Initialize Funding instance.
19 Args:
20 data: Raw funding data from API response
21 """
22 super().__init__(data)
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))
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))
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")
39 @property
40 def federal_account(self) -> Optional[str]:
41 """Identifier of the federal account."""
42 return self.get_value("federal_account")
44 @property
45 def account_title(self) -> Optional[str]:
46 """Federal account title."""
47 return self.get_value("account_title")
49 @property
50 def funding_agency_name(self) -> Optional[str]:
51 """Name of the funding agency."""
52 return self.get_value("funding_agency_name")
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
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
66 @property
67 def funding_agency_slug(self) -> Optional[str]:
68 """URL-friendly funding agency identifier."""
69 return self.get_value("funding_agency_slug")
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
80 @property
81 def awarding_agency_name(self) -> Optional[str]:
82 """Name of the awarding agency."""
83 return self.get_value("awarding_agency_name")
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
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
97 @property
98 def awarding_agency_slug(self) -> Optional[str]:
99 """URL-friendly awarding agency identifier."""
100 return self.get_value("awarding_agency_slug")
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
111 @property
112 def object_class(self) -> Optional[str]:
113 """Object class code."""
114 return self.get_value("object_class")
116 @property
117 def object_class_name(self) -> Optional[str]:
118 """Object class name/description."""
119 return self.get_value("object_class_name")
121 @property
122 def program_activity_code(self) -> Optional[str]:
123 """Program activity code."""
124 return self.get_value("program_activity_code")
126 @property
127 def program_activity_name(self) -> Optional[str]:
128 """Program activity name."""
129 return self.get_value("program_activity_name")
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
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
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
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")
154 def __repr__(self) -> str:
155 """String representation of Funding."""
156 year = self.reporting_fiscal_year or "?"
157 month = self.reporting_fiscal_month
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 = "?"
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 = "?"
174 agency = self.funding_agency_name or "Unknown Agency"
176 return f"<Funding {year}-{month_str} {agency} {amount_str}>"