Coverage for src/usaspending/models/contract.py: 83%

69 statements  

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

1"""Contract award model for USASpending data.""" 

2 

3from __future__ import annotations 

4from typing import Dict, Any, Optional, TYPE_CHECKING 

5from functools import cached_property 

6 

7from .award import Award 

8from ..utils.formatter import to_float 

9 

10if TYPE_CHECKING: 

11 from ..queries.subawards_search import SubAwardsSearch 

12 

13class Contract(Award): 

14 """Contract award type including definitive contracts and purchase orders.""" 

15 

16 TYPE_FIELDS = [ 

17 "piid", 

18 "base_exercised_options", 

19 "base_and_all_options", 

20 "contract_award_type", 

21 "naics_code", 

22 "naics_description", 

23 "naics_hierarchy", 

24 "psc_code", 

25 "psc_description", 

26 "psc_hierarchy", 

27 "latest_transaction_contract_data", 

28 ] 

29 

30 SEARCH_FIELDS = Award.SEARCH_FIELDS + [ 

31 "Start Date", 

32 "End Date", 

33 "Award Amount", 

34 "Total Outlays", 

35 "Contract Award Type", 

36 "NAICS", 

37 "PSC", 

38 ] 

39 

40 @property 

41 def piid(self) -> Optional[str]: 

42 """ 

43 Procurement Instrument Identifier - A unique identifier assigned to a federal 

44 contract, purchase order, basic ordering agreement, basic agreement, and 

45 blanket purchase agreement. It is used to track the contract, and any 

46 modifications or transactions related to it. After October 2017, it is 

47 between 13 and 17 digits, both letters and numbers. 

48 """ 

49 return self._lazy_get("piid") 

50 

51 @property 

52 def base_exercised_options(self) -> Optional[float]: 

53 """The sum of the base_exercised_options_val from associated transactions""" 

54 return to_float(self._lazy_get("base_exercised_options", default=None)) 

55 

56 @property 

57 def base_and_all_options(self) -> Optional[float]: 

58 """The sum of the base_and_all_options_value from associated transactions""" 

59 return to_float(self._lazy_get("base_and_all_options", default=None)) 

60 

61 @property 

62 def contract_award_type(self) -> Optional[str]: 

63 """Contract award type description.""" 

64 return self.type_description 

65 

66 @property 

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

68 """NAICS industry classification code.""" 

69 naics_data = self._lazy_get("naics", "NAICS") 

70 if isinstance(naics_data, dict): 

71 return naics_data.get("code") 

72 if self.naics_hierarchy and isinstance(self.naics_hierarchy.get("base_code"), dict): 

73 return self.naics_hierarchy["base_code"].get("code") 

74 if self.latest_transaction_contract_data: 

75 return self.latest_transaction_contract_data.get("naics") 

76 return None 

77 

78 @property 

79 def naics_description(self) -> Optional[str]: 

80 """NAICS industry classification description.""" 

81 naics_data = self._lazy_get("naics", "NAICS") 

82 if isinstance(naics_data, dict): 

83 return naics_data.get("description") 

84 if self.naics_hierarchy and isinstance(self.naics_hierarchy.get("base_code"), dict): 

85 return self.naics_hierarchy["base_code"].get("description") 

86 if self.latest_transaction_contract_data: 

87 return self.latest_transaction_contract_data.get("naics_description") 

88 return None 

89 

90 @property 

91 def psc_code(self) -> Optional[str]: 

92 """Product/Service Code (PSC) for contracts.""" 

93 psc_data = self._lazy_get("psc", "PSC") 

94 if isinstance(psc_data, dict): 

95 return psc_data.get("code") 

96 if self.psc_hierarchy and isinstance(self.psc_hierarchy.get("base_code"), dict): 

97 return self.psc_hierarchy["base_code"].get("code") 

98 return None 

99 

100 @property 

101 def psc_description(self) -> Optional[str]: 

102 """Product/Service Code (PSC) description.""" 

103 psc_data = self._lazy_get("psc", "PSC") 

104 if isinstance(psc_data, dict): 

105 return psc_data.get("description") 

106 if self.psc_hierarchy and isinstance(self.psc_hierarchy.get("base_code"), dict): 

107 return self.psc_hierarchy["base_code"].get("description") 

108 return None 

109 

110 @cached_property 

111 def psc_hierarchy(self) -> Optional[Dict[str, Any]]: 

112 """Product/Service Code (PSC) hierarchy information.""" 

113 return self._lazy_get("psc_hierarchy") 

114 

115 @cached_property 

116 def naics_hierarchy(self) -> Optional[Dict[str, Any]]: 

117 """North American Industry Classification System (NAICS) hierarchy.""" 

118 return self._lazy_get("naics_hierarchy") 

119 

120 @cached_property 

121 def latest_transaction_contract_data(self) -> Optional[Dict[str, Any]]: 

122 """Latest contract transaction data with procurement-specific details.""" 

123 return self._lazy_get("latest_transaction_contract_data") 

124 

125 @property 

126 def subawards(self) -> "SubAwardsSearch": 

127 """Get subawards query builder for this contract award with appropriate award type filters. 

128 

129 Returns a SubAwardsSearch object that can be further filtered and chained. 

130 Automatically applies contract award type filters. 

131 

132 Examples: 

133 >>> contract.subawards.count() # Get count without loading all data 

134 >>> contract.subawards.limit(10).all() # Get first 10 subawards 

135 >>> list(contract.subawards) # Iterate through all subawards 

136 """ 

137 from ..config import CONTRACT_CODES 

138 

139 return (self._client.subawards 

140 .for_award(self.generated_unique_award_id) 

141 .with_award_types(*CONTRACT_CODES))