Skip to content
Closed
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
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Added
- Vehicles: `list_vehicles` gained 21 explicit filter parameters and `ordering` per API 4.3.0: `vehicle_type`, `type_of_idc`, `contract_type`, `set_aside` (multi-value via `|`), `who_can_use`, `naics_code`, `psc_code`, `program_acronym`, `agency`, `organization_id`, `total_obligated_min`/`max`, `idv_count_min`/`max`, `order_count_min`/`max`, `fiscal_year`, `award_date_after`/`before`, `last_date_to_order_after`/`before`, `ordering` (whitelist: `vehicle_obligations`, `latest_award_date`, `total_obligated`, `award_date`, `last_date_to_order`, `fiscal_year`, `idv_count`, `order_count`).
- Vehicles: `list_vehicle_awardees` gained a `search` parameter for entity-aware full-text search across IDV fields and recipient entity details (API 4.3.0).
- Vehicles: New top-level fields on `Vehicle` shape — `is_synthetic_solicitation`, `program_acronym`, `idv_count`, `total_obligated`, `latest_award_date` — plus a bundled `metrics` expansion (12 lakehouse rollups) on detail (API 4.2.1).
- Shaping: New `organization(*)` expand on `Vehicle`, `Forecast`, `Grant`, `ITDashboardInvestment`, and `Protest` schemas — returns the canonical 7-key office payload (`organization_id`, `office_code`, `office_name`, `agency_code`, `agency_name`, `department_code`, `department_name`).
- Shaping: New `vehicle(*)` expand on `Contract` — request the parent vehicle inline from `/api/contracts/` (API 4.2.0).

### Changed
- Default vehicle shapes (`ShapeConfig.VEHICLES_MINIMAL` / `VEHICLES_COMPREHENSIVE`) updated to surface the new top-level fields; comprehensive default now includes `organization(*)` and `metrics(*)`.

## [0.5.0] - 2026-04-08

### Added
Expand Down
79 changes: 75 additions & 4 deletions tango/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,7 @@ def last_response_headers(self) -> httpx.Headers | None:
@staticmethod
def _parse_rate_limit_headers(headers: httpx.Headers) -> RateLimitInfo:
"""Extract rate limit info from response headers."""

def _int_or_none(val: str | None) -> int | None:
if val is None:
return None
Expand Down Expand Up @@ -1456,8 +1457,40 @@ def list_vehicles(
flat_lists: bool = False,
joiner: str = ".",
search: str | None = None,
vehicle_type: str | None = None,
type_of_idc: str | None = None,
contract_type: str | None = None,
set_aside: str | None = None,
who_can_use: str | None = None,
naics_code: int | None = None,
psc_code: str | None = None,
program_acronym: str | None = None,
agency: str | None = None,
organization_id: str | None = None,
total_obligated_min: float | int | Decimal | None = None,
total_obligated_max: float | int | Decimal | None = None,
idv_count_min: int | None = None,
idv_count_max: int | None = None,
order_count_min: int | None = None,
order_count_max: int | None = None,
fiscal_year: int | None = None,
award_date_after: str | date | datetime | None = None,
award_date_before: str | date | datetime | None = None,
last_date_to_order_after: str | date | datetime | None = None,
last_date_to_order_before: str | date | datetime | None = None,
ordering: str | None = None,
) -> PaginatedResponse:
"""List Vehicles (solicitation-centric groupings of IDVs)."""
"""List Vehicles (solicitation-centric groupings of IDVs).

Multi-value filters (``vehicle_type``, ``type_of_idc``, ``contract_type``,
``set_aside``) accept pipe-separated values for OR semantics, e.g.
``vehicle_type="A|B|C"``.

``ordering`` accepts: ``vehicle_obligations``, ``latest_award_date``,
``total_obligated``, ``award_date``, ``last_date_to_order``,
``fiscal_year``, ``idv_count``, ``order_count``. Prefix with ``-`` for
descending.
"""
params: dict[str, Any] = {"page": page, "limit": min(limit, 100)}

if shape is None:
Expand All @@ -1471,8 +1504,37 @@ def list_vehicles(
if flat_lists:
params["flat_lists"] = "true"

if search:
params["search"] = search
for k, val in (
("search", search),
("vehicle_type", vehicle_type),
("type_of_idc", type_of_idc),
("contract_type", contract_type),
("set_aside", set_aside),
("who_can_use", who_can_use),
("naics_code", naics_code),
("psc_code", psc_code),
("program_acronym", program_acronym),
("agency", agency),
("organization_id", organization_id),
("total_obligated_min", total_obligated_min),
("total_obligated_max", total_obligated_max),
("idv_count_min", idv_count_min),
("idv_count_max", idv_count_max),
("order_count_min", order_count_min),
("order_count_max", order_count_max),
("fiscal_year", fiscal_year),
("award_date_after", award_date_after),
("award_date_before", award_date_before),
("last_date_to_order_after", last_date_to_order_after),
("last_date_to_order_before", last_date_to_order_before),
("ordering", ordering),
):
if val is None:
continue
if isinstance(val, (date, datetime)):
params[k] = val.isoformat()
else:
params[k] = val

data = self._get("/api/vehicles/", params)

Expand Down Expand Up @@ -1531,8 +1593,14 @@ def list_vehicle_awardees(
flat: bool = False,
flat_lists: bool = False,
joiner: str = ".",
search: str | None = None,
) -> PaginatedResponse:
"""List the IDV awardees for a Vehicle (`/api/vehicles/{uuid}/awardees/`)."""
"""List the IDV awardees for a Vehicle (`/api/vehicles/{uuid}/awardees/`).

``search`` runs entity-aware full-text search across IDV fields
(PIID, key, solicitation_identifier, NAICS, PSC, idv_type,
fiscal_year) and recipient entity details (name, address).
"""
params: dict[str, Any] = {"page": page, "limit": min(limit, 100)}

if shape is None:
Expand All @@ -1546,6 +1614,9 @@ def list_vehicle_awardees(
if flat_lists:
params["flat_lists"] = "true"

if search:
params["search"] = search

data = self._get(f"/api/vehicles/{uuid}/awardees/", params)

results = [
Expand Down
11 changes: 7 additions & 4 deletions tango/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -690,16 +690,19 @@ class ShapeConfig:

# Default for list_vehicles()
VEHICLES_MINIMAL: Final = (
"uuid,solicitation_identifier,organization_id,awardee_count,order_count,"
"uuid,solicitation_identifier,is_synthetic_solicitation,program_acronym,"
"organization_id,idv_count,awardee_count,order_count,total_obligated,"
"vehicle_obligations,vehicle_contracts_value,solicitation_title,solicitation_date"
)

# Default for get_vehicle()
VEHICLES_COMPREHENSIVE: Final = (
"uuid,solicitation_identifier,agency_id,organization_id,vehicle_type,who_can_use,"
"uuid,solicitation_identifier,is_synthetic_solicitation,program_acronym,"
"agency_id,organization_id,vehicle_type,who_can_use,"
"solicitation_title,solicitation_description,solicitation_date,naics_code,psc_code,set_aside,"
"fiscal_year,award_date,last_date_to_order,awardee_count,order_count,vehicle_obligations,vehicle_contracts_value,"
"type_of_idc,contract_type,competition_details(*)"
"fiscal_year,award_date,last_date_to_order,idv_count,awardee_count,order_count,"
"total_obligated,vehicle_obligations,vehicle_contracts_value,"
"type_of_idc,contract_type,competition_details(*),organization(*),metrics(*)"
)

# Default for list_vehicle_awardees()
Expand Down
Loading
Loading