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
« prev ^ index » next coverage.py v7.10.6, created at 2025-09-03 17:15 -0700
1"""Contract award model for USASpending data."""
3from __future__ import annotations
4from typing import Dict, Any, Optional, 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 Contract(Award):
14 """Contract award type including definitive contracts and purchase orders."""
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 ]
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 ]
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")
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))
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))
61 @property
62 def contract_award_type(self) -> Optional[str]:
63 """Contract award type description."""
64 return self.type_description
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
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
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
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
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")
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")
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")
125 @property
126 def subawards(self) -> "SubAwardsSearch":
127 """Get subawards query builder for this contract award with appropriate award type filters.
129 Returns a SubAwardsSearch object that can be further filtered and chained.
130 Automatically applies contract award type filters.
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
139 return (self._client.subawards
140 .for_award(self.generated_unique_award_id)
141 .with_award_types(*CONTRACT_CODES))