Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Added

- **`commercial_item_acquisition_procedures` filter on `list_contracts()` and `list_idvs()`** (Tango API 5.10.0). Filters by the FPDS commercial item acquisition procedures code: `A` (commercial products/services), `B` (products or services pursuant to FAR 12.102(f)), `C` (services pursuant to FAR 12.102(g)), `D` (commercial procedures not used) or `E` (DoD Section 803 Commercial Solutions Opening). Codes are case-insensitive and pipe-separated for OR, e.g. `"A|E"`; an unknown or multi-character code matches nothing. The API does not offer it on OTAs or OTIDVs, so `list_otas()` and `list_otidvs()` do not take it. The field itself was already returned in responses.

### Changed

- Re-vendored `contracts/filter_shape_contract.json` (Tango API 5.10.0).

## [1.16.0] - 2026-10-10

### Added
Expand Down
49 changes: 48 additions & 1 deletion contracts/filter_shape_contract.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"meta": {
"api_version": "5.9.1",
"api_version": "5.10.0",
"description": "Canonical API filter/shape contract. Downstream consumers (SDK, MCP) should validate their conformance against this manifest.",
"generated_from": "scripts/filter_shape_conformance.py",
"schema_version": 2
Expand Down Expand Up @@ -95,6 +95,7 @@
"award_date_lte",
"award_type",
"awarding_agency",
"commercial_item_acquisition_procedures",
"expiring_gte",
"expiring_lte",
"fiscal_year",
Expand Down Expand Up @@ -150,6 +151,10 @@
"filter_class": "PerformantAgencyFilter",
"type": "string"
},
"commercial_item_acquisition_procedures": {
"filter_class": "BaseSmartFilter",
"type": "string"
},
"expiring_gte": {
"filter_class": "DateFilter",
"lookup": "gte",
Expand Down Expand Up @@ -837,6 +842,7 @@
"award_date_lte",
"award_type",
"awarding_agency",
"commercial_item_acquisition_procedures",
"expiring_gte",
"expiring_lte",
"fiscal_year",
Expand Down Expand Up @@ -892,6 +898,10 @@
"filter_class": "PerformantAgencyFilter",
"type": "string"
},
"commercial_item_acquisition_procedures": {
"filter_class": "BaseSmartFilter",
"type": "string"
},
"expiring_gte": {
"filter_class": "DateFilter",
"lookup": "gte",
Expand Down Expand Up @@ -2701,6 +2711,7 @@
"award_date_lte",
"award_type",
"awarding_agency",
"commercial_item_acquisition_procedures",
"expiring_gte",
"expiring_lte",
"fiscal_year",
Expand Down Expand Up @@ -2756,6 +2767,10 @@
"filter_class": "PerformantAgencyFilter",
"type": "string"
},
"commercial_item_acquisition_procedures": {
"filter_class": "BaseSmartFilter",
"type": "string"
},
"expiring_gte": {
"filter_class": "DateFilter",
"lookup": "gte",
Expand Down Expand Up @@ -3434,6 +3449,7 @@
"award_date_lte",
"award_type",
"awarding_agency",
"commercial_item_acquisition_procedures",
"cursor",
"expiring_gte",
"expiring_lte",
Expand Down Expand Up @@ -4936,6 +4952,7 @@
"award_date_lte",
"award_type",
"awarding_agency",
"commercial_item_acquisition_procedures",
"expiring_gte",
"expiring_lte",
"fiscal_year",
Expand Down Expand Up @@ -4991,6 +5008,10 @@
"filter_class": "PerformantAgencyFilter",
"type": "string"
},
"commercial_item_acquisition_procedures": {
"filter_class": "BaseSmartFilter",
"type": "string"
},
"expiring_gte": {
"filter_class": "DateFilter",
"lookup": "gte",
Expand Down Expand Up @@ -5677,6 +5698,7 @@
"award_date_gte",
"award_date_lte",
"awarding_agency",
"commercial_item_acquisition_procedures",
"expiring_gte",
"expiring_lte",
"fiscal_year",
Expand Down Expand Up @@ -5716,6 +5738,10 @@
"filter_class": "PerformantAgencyFilter",
"type": "string"
},
"commercial_item_acquisition_procedures": {
"filter_class": "BaseSmartFilter",
"type": "string"
},
"expiring_gte": {
"filter_class": "DateFilter",
"lookup": "gte",
Expand Down Expand Up @@ -8629,6 +8655,7 @@
"award_date_gte",
"award_date_lte",
"awarding_agency",
"commercial_item_acquisition_procedures",
"expiring_gte",
"expiring_lte",
"fiscal_year",
Expand Down Expand Up @@ -8668,6 +8695,10 @@
"filter_class": "PerformantAgencyFilter",
"type": "string"
},
"commercial_item_acquisition_procedures": {
"filter_class": "BaseSmartFilter",
"type": "string"
},
"expiring_gte": {
"filter_class": "DateFilter",
"lookup": "gte",
Expand Down Expand Up @@ -9220,6 +9251,7 @@
"award_date_gte",
"award_date_lte",
"awarding_agency",
"commercial_item_acquisition_procedures",
"cursor",
"expiring_gte",
"expiring_lte",
Expand Down Expand Up @@ -9260,6 +9292,7 @@
"award_date_lte",
"award_type",
"awarding_agency",
"commercial_item_acquisition_procedures",
"expiring_gte",
"expiring_lte",
"fiscal_year",
Expand Down Expand Up @@ -9315,6 +9348,10 @@
"filter_class": "PerformantAgencyFilter",
"type": "string"
},
"commercial_item_acquisition_procedures": {
"filter_class": "BaseSmartFilter",
"type": "string"
},
"expiring_gte": {
"filter_class": "DateFilter",
"lookup": "gte",
Expand Down Expand Up @@ -10001,6 +10038,7 @@
"award_date_gte",
"award_date_lte",
"awarding_agency",
"commercial_item_acquisition_procedures",
"expiring_gte",
"expiring_lte",
"fiscal_year",
Expand Down Expand Up @@ -10040,6 +10078,10 @@
"filter_class": "PerformantAgencyFilter",
"type": "string"
},
"commercial_item_acquisition_procedures": {
"filter_class": "BaseSmartFilter",
"type": "string"
},
"expiring_gte": {
"filter_class": "DateFilter",
"lookup": "gte",
Expand Down Expand Up @@ -16717,6 +16759,7 @@
"award_date_lte",
"award_type",
"awarding_agency",
"commercial_item_acquisition_procedures",
"expiring_gte",
"expiring_lte",
"fiscal_year",
Expand Down Expand Up @@ -16772,6 +16815,10 @@
"filter_class": "PerformantAgencyFilter",
"type": "string"
},
"commercial_item_acquisition_procedures": {
"filter_class": "BaseSmartFilter",
"type": "string"
},
"expiring_gte": {
"filter_class": "DateFilter",
"lookup": "gte",
Expand Down
6 changes: 5 additions & 1 deletion docs/API_REFERENCE.md
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,7 @@ contracts = client.list_contracts(
naics_code=None, # Mapped to 'naics' API param
psc_code=None, # Mapped to 'psc' API param
set_aside_type=None, # Mapped to 'set_aside' API param
commercial_item_acquisition_procedures=None, # FPDS code A-E, e.g. "A|E"
# Type filters
fiscal_year=None,
fiscal_year_gte=None,
Expand Down Expand Up @@ -290,6 +291,7 @@ contracts = client.list_contracts(
- `naics_code` - NAICS industry code (mapped to 'naics' API param)
- `psc_code` - Product/Service code (mapped to 'psc' API param)
- `set_aside_type` - Set-aside type (mapped to 'set_aside' API param)
- `commercial_item_acquisition_procedures` - FPDS commercial item acquisition procedures code, case-insensitive: `A` (commercial products/services), `B` (products or services pursuant to FAR 12.102(f)), `C` (services pursuant to FAR 12.102(g)), `D` (commercial procedures not used) or `E` (DoD Section 803 Commercial Solutions Opening). Multi-value OR via `|`, e.g. `"A|E"`. An unknown or multi-character code matches nothing.

**Type Filters:**
- `fiscal_year` - Federal fiscal year (exact match)
Expand Down Expand Up @@ -420,7 +422,7 @@ otas = client.list_otas(

**Notes:**
- Uses **keyset pagination** (`cursor` + `limit`) rather than page numbers.
- Filter parameters mirror those on `list_contracts`.
- Filter parameters mirror those on `list_contracts`, except `commercial_item_acquisition_procedures`, which the API does not offer on OTAs or OTIDVs.

**Returns:** [PaginatedResponse](#paginatedresponse) with OTA dictionaries

Expand Down Expand Up @@ -638,12 +640,14 @@ idvs = client.list_idvs(
cursor=None,
shape=ShapeConfig.IDVS_MINIMAL,
awarding_agency="4700",
commercial_item_acquisition_procedures="E", # DoD Section 803 CSO
)
```

Notes:

- This endpoint uses **keyset pagination** (`cursor` + `limit`) rather than page numbers.
- `commercial_item_acquisition_procedures` takes the same FPDS codes `A`–`E` as on `list_contracts()`, with `|` for OR.

### get_idv()

Expand Down
7 changes: 7 additions & 0 deletions tango/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -632,6 +632,7 @@ def list_contracts(
award_date_lte: str | None = None,
award_type: str | None = None,
awarding_agency: str | None = None,
commercial_item_acquisition_procedures: str | None = None,
expiring_gte: str | None = None,
expiring_lte: str | None = None,
fiscal_year: int | None = None,
Expand Down Expand Up @@ -674,6 +675,7 @@ def list_contracts(
award_date_lte: Award date <= (YYYY-MM-DD)
award_type: Award type code
awarding_agency: Awarding agency code (e.g., "4700" for GSA)
commercial_item_acquisition_procedures: FPDS commercial item acquisition procedures code, case-insensitive: "A" (commercial products/services), "B" (products or services pursuant to FAR 12.102(f)), "C" (services pursuant to FAR 12.102(g)), "D" (commercial procedures not used) or "E" (DoD Section 803 Commercial Solutions Opening). Pipe-separate codes for OR, e.g. "A|E"; an unknown code matches nothing.
expiring_gte: Expiring on or after date
expiring_lte: Expiring on or before date
fiscal_year: Fiscal year (exact match)
Expand Down Expand Up @@ -751,6 +753,7 @@ def list_contracts(
("award_date_lte", award_date_lte),
("award_type", award_type),
("awarding_agency", awarding_agency),
("commercial_item_acquisition_procedures", commercial_item_acquisition_procedures),
("expiring_gte", expiring_gte),
("expiring_lte", expiring_lte),
("fiscal_year", fiscal_year),
Expand Down Expand Up @@ -915,6 +918,7 @@ def list_idvs(
award_date_gte: str | None = None,
award_date_lte: str | None = None,
awarding_agency: str | None = None,
commercial_item_acquisition_procedures: str | None = None,
expiring_gte: str | None = None,
expiring_lte: str | None = None,
fiscal_year: int | None = None,
Expand All @@ -941,6 +945,8 @@ def list_idvs(

This mirrors `/api/idvs/` and supports the same filter parameters as the API,
plus shaping via `shape`.

`commercial_item_acquisition_procedures` takes an FPDS commercial item acquisition procedures code `A`–`E` (case-insensitive; `E` is DoD Section 803 Commercial Solutions Opening), pipe-separated for OR, e.g. `"A|E"`; an unknown code matches nothing.
"""
params: dict[str, Any] = {"limit": min(limit, 100)}
if cursor:
Expand All @@ -962,6 +968,7 @@ def list_idvs(
("award_date_gte", award_date_gte),
("award_date_lte", award_date_lte),
("awarding_agency", awarding_agency),
("commercial_item_acquisition_procedures", commercial_item_acquisition_procedures),
("expiring_gte", expiring_gte),
("expiring_lte", expiring_lte),
("fiscal_year", fiscal_year),
Expand Down
27 changes: 27 additions & 0 deletions tests/test_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -2247,6 +2247,33 @@ def test_list_entities_omits_unset_socioeconomic(self, mock_request):
assert "socioeconomic" not in mock_request.call_args[1]["params"]


class TestCommercialItemAcquisitionProceduresFilter:
@pytest.mark.parametrize("method", ["list_contracts", "list_idvs"])
@patch("tango.client.httpx.Client.request")
def test_forwards_pipe_separated_codes_verbatim(self, mock_request, method):
_stub_empty_page(mock_request)
getattr(TangoClient(api_key="test-key"), method)(
commercial_item_acquisition_procedures="a|E"
)
assert (
mock_request.call_args[1]["params"]["commercial_item_acquisition_procedures"] == "a|E"
)

@pytest.mark.parametrize("method", ["list_contracts", "list_idvs"])
@patch("tango.client.httpx.Client.request")
def test_omits_unset_filter(self, mock_request, method):
_stub_empty_page(mock_request)
getattr(TangoClient(api_key="test-key"), method)()
assert "commercial_item_acquisition_procedures" not in mock_request.call_args[1]["params"]

@pytest.mark.parametrize("method", ["list_otas", "list_otidvs"])
def test_not_accepted_on_other_transaction_lists(self, method):
with pytest.raises(TypeError, match="commercial_item_acquisition_procedures"):
getattr(TangoClient(api_key="test-key"), method)(
commercial_item_acquisition_procedures="E"
)


class TestAgencyFilterDiagnostics:
"""`meta` from the API's agency-filter diagnostics.

Expand Down
Loading