Coverage for src/usaspending/models/idv.py: 79%
66 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"""IDV (Indefinite Delivery Vehicle) award model for USASpending data."""
3from __future__ import annotations
4from typing import Dict, Any, Optional
5from functools import cached_property
7from .award import Award
8from .location import Location
9from ..utils.formatter import to_float
11class IDV(Award):
12 """Indefinite Delivery Vehicle (IDV) award type.
14 IDVs are contract vehicles that provide for an indefinite quantity of supplies
15 or services during a fixed period of time. They establish broad parameters and
16 terms for ordering supplies/services, with specific orders placed against them
17 via delivery orders or task orders.
19 Common IDV types include:
20 - GWAC (Government-Wide Acquisition Contract)
21 - IDC (Indefinite Delivery Contract)
22 - FSS (Federal Supply Schedule)
23 - BOA (Basic Ordering Agreement)
24 - BPA (Blanket Purchase Agreement)
26 IDVs serve as parent contracts that streamline procurement by pre-negotiating
27 terms, conditions, and pricing for future orders. They reduce administrative
28 costs and enable faster acquisition of recurring needs.
30 Example:
31 >>> # Find all IDVs for an agency
32 >>> idvs = client.awards.search().idvs().for_agency("NASA").all()
33 >>> for idv in idvs:
34 ... print(f"{idv.piid}: {idv.recipient_name} - ${idv.total_obligation:,.2f}")
35 """
37 TYPE_FIELDS = [
38 "piid",
39 "base_and_all_options",
40 "contract_award_type",
41 "naics_code",
42 "naics_description",
43 "naics_hierarchy",
44 "psc_code",
45 "psc_description",
46 "psc_hierarchy",
47 "latest_transaction_contract_data",
48 ]
50 SEARCH_FIELDS = Award.SEARCH_FIELDS + [
51 "Start Date",
52 "Award Amount",
53 "Total Outlays",
54 "Contract Award Type",
55 "Last Date to Order",
56 "NAICS",
57 "PSC",
58 ]
60 @property
61 def piid(self) -> Optional[str]:
62 """
63 Procurement Instrument Identifier - A unique identifier assigned to a federal
64 contract, purchase order, basic ordering agreement, basic agreement, and
65 blanket purchase agreement. It is used to track the contract, and any
66 modifications or transactions related to it. After October 2017, it is
67 between 13 and 17 digits, both letters and numbers.
68 """
69 return self._lazy_get("piid")
71 @property
72 def base_and_all_options(self) -> Optional[float]:
73 """
74 For IDVs, this is the mutually agreed upon total contract value including all options (if any)
75 AND the estimated value of all potential orders. For modifications enter the CHANGE, positive
76 or negative of these values, if any.
77 """
78 return to_float(self._lazy_get("base_and_all_options", default=None))
80 @property
81 def base_exercised_options(self) -> Optional[float]:
82 """
83 The contract value for the base contract and any options that have been exercised.
84 """
85 return to_float(self._lazy_get("base_exercised_options", default=None))
87 @property
88 def contract_award_type(self) -> Optional[str]:
89 """Contract award type description."""
90 return self._lazy_get("contract_award_type", "Contract Award Type", "type_description")
92 @property
93 def naics_code(self) -> Optional[str]:
94 """NAICS industry classification code."""
95 naics_data = self._lazy_get("naics", "NAICS")
96 if isinstance(naics_data, dict):
97 return naics_data.get("code")
98 if self.naics_hierarchy and isinstance(self.naics_hierarchy.get("base_code"), dict):
99 return self.naics_hierarchy["base_code"].get("code")
100 return None
102 @property
103 def naics_description(self) -> Optional[str]:
104 """NAICS industry classification description."""
105 naics_data = self._lazy_get("naics", "NAICS")
106 if isinstance(naics_data, dict):
107 return naics_data.get("description")
108 return None
110 @property
111 def psc_code(self) -> Optional[str]:
112 """Product/Service Code (PSC) for contracts."""
113 psc_data = self._lazy_get("psc", "PSC")
114 if isinstance(psc_data, dict):
115 return psc_data.get("code")
116 if self.psc_hierarchy and isinstance(self.psc_hierarchy.get("base_code"), dict):
117 return self.psc_hierarchy["base_code"].get("code")
118 return None
120 @property
121 def psc_description(self) -> Optional[str]:
122 """Product/Service Code (PSC) description."""
123 psc_data = self._lazy_get("psc", "PSC")
124 if isinstance(psc_data, dict):
125 return psc_data.get("description")
126 return None
128 @cached_property
129 def psc_hierarchy(self) -> Optional[Dict[str, Any]]:
130 """Product/Service Code (PSC) hierarchy information."""
131 return self._lazy_get("psc_hierarchy")
133 @cached_property
134 def naics_hierarchy(self) -> Optional[Dict[str, Any]]:
135 """North American Industry Classification System (NAICS) hierarchy."""
136 return self._lazy_get("naics_hierarchy")
138 @cached_property
139 def latest_transaction_contract_data(self) -> Optional[Dict[str, Any]]:
140 """Latest contract transaction data with procurement-specific details."""
141 return self._lazy_get("latest_transaction_contract_data")
143 @cached_property
144 def place_of_performance(self) -> Optional[Location]:
145 """Award place of performance location.
147 Note: IDVs typically have null or empty place_of_performance data.
148 """
149 data = self._lazy_get(
150 "place_of_performance", "Primary Place of Performance", default=None
151 )
152 if not isinstance(data, dict) or not data:
153 return None
155 # Check if all values in the dict are None/null (common for IDV awards)
156 if all(v is None for v in data.values()):
157 return None
159 return Location(data, self._client)