diff --git a/CHANGELOG.md b/CHANGELOG.md index 8fbc51b..9c143b8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,20 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Added +- Vehicles: new top-level fields `program_acronym`, `idv_count`, `total_obligated`, `is_synthetic_solicitation`, `latest_award_date`, `description`, `opportunity_id`, and a live nested `organization` object (awarding-org snapshot) — tracking the upstream tango vehicles surface. +- Vehicles: new `metrics(*)` shape expansion bundling 12 computed metrics: `avg_offers_received`, `award_concentration_hhi`, `order_concentration_hhi`, `competed_rate`, `using_agency_count`, `avg_order_value`, `max_order_value`, `top_recipient_share`, `recent_obligations_24mo`, `recent_orders_24mo`, `days_since_last_order`, `obligation_to_ceiling_ratio`. Backed by a new `VehicleMetrics` schema. +- `list_vehicle_orders(uuid, ...)` for the new `/api/vehicles/{uuid}/orders/` endpoint, returning task orders under the vehicle's IDVs with two-phase pagination. +- `ordering` parameter on `list_vehicles` (whitelist: `vehicle_obligations`, `latest_award_date`; prefix `-` for descending) and on `list_vehicle_orders` (whitelist: `award_date`, `obligated`, `total_contract_value`). +- `ShapeConfig.VEHICLE_ORDERS_MINIMAL` default for the new orders endpoint. +- `Vehicle` and `VehicleMetrics` are now exported from the top-level `tango` package. + +### Changed +- `ShapeConfig.VEHICLES_MINIMAL` and `VEHICLES_COMPREHENSIVE` now include the new top-level fields and the `organization` expansion. `VEHICLES_COMPREHENSIVE` defaults to `metrics(*)` and no longer pulls the deprecated `competition_details(*)` blob. + +### Deprecated +- Vehicles shape fields `agency_details`, `competition_details`, and the `opportunity` expansion. The upstream API now sends a `Deprecation: true` header for these and recomputes them at request time. Explicit use in `shape=...` emits a Python `DeprecationWarning`. Sunset timeline TBD upstream. + ## [0.5.0] - 2026-04-08 ### Added diff --git a/README.md b/README.md index d154abf..d08d113 100644 --- a/README.md +++ b/README.md @@ -169,9 +169,14 @@ otidvs = client.list_otidvs(limit=25) ### Vehicles ```python -vehicles = client.list_vehicles(search="GSA schedule", shape=ShapeConfig.VEHICLES_MINIMAL) +vehicles = client.list_vehicles( + search="GSA schedule", + ordering="-vehicle_obligations", + shape=ShapeConfig.VEHICLES_MINIMAL, +) vehicle = client.get_vehicle("UUID", shape=ShapeConfig.VEHICLES_COMPREHENSIVE) awardees = client.list_vehicle_awardees("UUID") +orders = client.list_vehicle_orders("UUID", ordering="-obligated") ``` ### Entities (Vendors/Recipients) diff --git a/docs/API_REFERENCE.md b/docs/API_REFERENCE.md index e1d58ee..c4122b2 100644 --- a/docs/API_REFERENCE.md +++ b/docs/API_REFERENCE.md @@ -503,13 +503,14 @@ Vehicles provide a solicitation-centric way to discover groups of related IDVs a ### list_vehicles() -List vehicles with optional vehicle-level full-text search. +List vehicles with optional vehicle-level full-text search and ordering. ```python vehicles = client.list_vehicles( page=1, limit=25, search="GSA schedule", + ordering="-vehicle_obligations", shape=ShapeConfig.VEHICLES_MINIMAL, flat=False, flat_lists=False, @@ -520,6 +521,7 @@ vehicles = client.list_vehicles( - `page` (int): Page number (default: 1) - `limit` (int): Results per page (default: 25, max: 100) - `search` (str, optional): Vehicle-level search term +- `ordering` (str, optional): Server-side sort. Allowed: `vehicle_obligations`, `latest_award_date`. Prefix with `-` for descending. - `shape` (str, optional): Shape string (defaults to `ShapeConfig.VEHICLES_MINIMAL`) - `flat` (bool): Flatten nested objects in shaped response - `flat_lists` (bool): Flatten arrays using indexed keys @@ -552,6 +554,67 @@ awardees = client.list_vehicle_awardees( ) ``` +### list_vehicle_orders() + +List task orders under a vehicle's IDVs (`/api/vehicles/{uuid}/orders/`). Optimized for fast pagination over large vehicles. + +```python +orders = client.list_vehicle_orders( + uuid="00000000-0000-0000-0000-000000000001", + limit=25, + ordering="-obligated", + shape=ShapeConfig.VEHICLE_ORDERS_MINIMAL, +) +``` + +**Parameters:** +- `uuid` (str): Vehicle UUID +- `page` (int): Page number (default: 1) +- `limit` (int): Results per page (default: 25, max: 100) +- `ordering` (str, optional): Server-side sort. Allowed: `award_date` (default), `obligated`, `total_contract_value`. Prefix with `-` for descending. +- `shape` (str, optional): Shape string (defaults to `ShapeConfig.VEHICLE_ORDERS_MINIMAL`) +- `flat`, `flat_lists`, `joiner`: as on other vehicles methods + +**Returns:** [PaginatedResponse](#paginatedresponse) with order (Contract) dictionaries + +### Vehicle response fields + +The post-cutover (May 2026) vehicle response includes these top-level fields, all addressable via the `shape` parameter: + +| Field | Type | Notes | +| ----- | ---- | ----- | +| `uuid` | str | Stable identifier. | +| `solicitation_identifier` | str | Solicitation shared by underlying IDVs. | +| `is_synthetic_solicitation` | bool | `True` for GWAC orphans recovered via `ACRO:` prefix. | +| `agency_id` | str | From IDV award-key suffix. | +| `program_acronym` | str \| None | New post-cutover field. | +| `organization_id` | str \| None | Awarding organization. | +| `organization` | dict \| None | Live awarding-org snapshot `{organization_id, office_code, office_name, agency_code, agency_name, department_code, department_name}`. Selected as a leaf field (`shape=...,organization`); not currently sub-selectable. | +| `vehicle_type`, `who_can_use`, `type_of_idc`, `contract_type` | dict \| None | Returned as `{code, description}`. | +| `description` | str \| None | Common text across IDV descriptions. | +| `descriptions` | list[str] \| None | Distinct IDV descriptions. | +| `idv_count`, `awardee_count`, `order_count` | int \| None | Denormalized rollups. | +| `total_obligated`, `vehicle_obligations`, `vehicle_contracts_value` | Decimal \| None | Denormalized rollups. | +| `award_date`, `latest_award_date`, `last_date_to_order` | date \| None | | +| `solicitation_title`, `solicitation_description`, `solicitation_date`, `opportunity_id` | str / date / None | From SAM.gov via the linked Opportunity. | +| `naics_code`, `psc_code`, `set_aside`, `fiscal_year` | int / str / None | | + +### Vehicle shape expansions + +- `awardees(...)` — underlying IDV awards. Supports nested `orders(...)`. +- `metrics(*)` — bundled computed metrics: `avg_offers_received`, `award_concentration_hhi`, `order_concentration_hhi`, `competed_rate`, `using_agency_count`, `avg_order_value`, `max_order_value`, `top_recipient_share`, `recent_obligations_24mo`, `recent_orders_24mo`, `days_since_last_order`, `obligation_to_ceiling_ratio`. Defaults included in `ShapeConfig.VEHICLES_COMPREHENSIVE`. +- `organization` — live awarding-org snapshot (selected as a leaf field; not sub-selectable). + +### Deprecated shape fields + +The following fields and expansions are still served by the API (recomputed at request time from the underlying IDVs) but the API now returns a `Deprecation: true` response header for them. They will be removed in a future tango API release. + +- `agency_details` (top-level field and `agency_details(*)` expansion) +- `competition_details` (top-level field and `competition_details(*)` expansion) +- `opportunity(*)` expansion (use the new top-level `solicitation_*` and `opportunity_id` fields instead) + +If you pass any of these in `shape=...`, the SDK will emit a Python `DeprecationWarning`. The default shapes (`VEHICLES_MINIMAL`, `VEHICLES_COMPREHENSIVE`) no longer include them. + --- ## IDVs diff --git a/docs/SHAPES.md b/docs/SHAPES.md index 6504733..2334170 100644 --- a/docs/SHAPES.md +++ b/docs/SHAPES.md @@ -57,7 +57,9 @@ idvs = client.list_idvs(shape=ShapeConfig.IDVS_MINIMAL) grants = client.list_grants(shape=ShapeConfig.GRANTS_MINIMAL) ``` -**Available constants:** Contracts (`CONTRACTS_MINIMAL`), Entities (`ENTITIES_MINIMAL`, `ENTITIES_COMPREHENSIVE`), Forecasts, Opportunities, Notices, Grants, IDVs, Vehicles, Organizations, OTAs, OTIDVs, Subawards. See [API Reference – ShapeConfig](API_REFERENCE.md#shapeconfig-predefined-shapes) for the full table and which method uses which constant. +**Available constants:** Contracts (`CONTRACTS_MINIMAL`), Entities (`ENTITIES_MINIMAL`, `ENTITIES_COMPREHENSIVE`), Forecasts, Opportunities, Notices, Grants, IDVs, Vehicles (`VEHICLES_MINIMAL`, `VEHICLES_COMPREHENSIVE`, `VEHICLE_AWARDEES_MINIMAL`, `VEHICLE_ORDERS_MINIMAL`), Organizations, OTAs, OTIDVs, Subawards. See [API Reference – ShapeConfig](API_REFERENCE.md#shapeconfig-predefined-shapes) for the full table and which method uses which constant. + +> **Vehicles `metrics(*)` expansion:** The vehicles surface bundles 12 computed metrics under a single `metrics(*)` expansion (e.g. `award_concentration_hhi`, `competed_rate`, `top_recipient_share`). It is included in `VEHICLES_COMPREHENSIVE` by default. The `agency_details`, `competition_details`, and `opportunity` shape entries are deprecated and emit `DeprecationWarning` if requested explicitly. ## Basic Shaping diff --git a/pyproject.toml b/pyproject.toml index 640b5c2..1fa0a1d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "hatchling.build" [project] name = "tango-python" -version = "0.5.0" +version = "0.6.0" description = "Python SDK for the Tango API" readme = "README.md" requires-python = ">=3.12" diff --git a/tango/__init__.py b/tango/__init__.py index ff0d6f5..3955756 100644 --- a/tango/__init__.py +++ b/tango/__init__.py @@ -15,6 +15,8 @@ RateLimitInfo, SearchFilters, ShapeConfig, + Vehicle, + VehicleMetrics, WebhookEndpoint, WebhookEventType, WebhookEventTypesResponse, @@ -29,7 +31,7 @@ TypeGenerator, ) -__version__ = "0.5.0" +__version__ = "0.6.0" __all__ = [ "TangoClient", "TangoAPIError", @@ -43,6 +45,8 @@ "PaginatedResponse", "SearchFilters", "ShapeConfig", + "Vehicle", + "VehicleMetrics", "WebhookEndpoint", "WebhookEventType", "WebhookEventTypesResponse", diff --git a/tango/client.py b/tango/client.py index 2d29c36..7f3d9b5 100644 --- a/tango/client.py +++ b/tango/client.py @@ -1,6 +1,7 @@ """Tango API Client""" import os +import warnings from datetime import date, datetime from decimal import Decimal from typing import Any @@ -123,6 +124,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 @@ -1447,6 +1449,40 @@ def get_itdashboard_investment( # Vehicles (Awards) # ============================================================================ + @staticmethod + def _warn_deprecated_vehicle_shape(shape: str | None) -> None: + # Upstream sends `Deprecation: true` for these fields/expansions; warn + # callers who request them explicitly so they have time to migrate + # before tango publishes a Sunset timeline. + from tango.shapes.explicit_schemas import DEPRECATED_VEHICLE_SHAPE_FIELDS + + if not shape: + return + # Match top-level field tokens, ignoring nesting inside parentheses. + depth = 0 + token = "" + tokens: list[str] = [] + for ch in shape: + if ch == "(": + depth += 1 + elif ch == ")": + depth = max(0, depth - 1) + elif ch == "," and depth == 0: + tokens.append(token.strip()) + token = "" + continue + token += ch + tokens.append(token.strip()) + used = {t.split("(", 1)[0] for t in tokens} & DEPRECATED_VEHICLE_SHAPE_FIELDS + if used: + warnings.warn( + f"Vehicle shape field(s) {sorted(used)!r} are deprecated upstream " + "and may be removed in a future tango API version. The API currently " + "returns a `Deprecation: true` header for these.", + DeprecationWarning, + stacklevel=3, + ) + def list_vehicles( self, page: int = 1, @@ -1456,12 +1492,20 @@ def list_vehicles( flat_lists: bool = False, joiner: str = ".", search: str | None = None, + ordering: str | None = None, ) -> PaginatedResponse: - """List Vehicles (solicitation-centric groupings of IDVs).""" + """List Vehicles (solicitation-centric groupings of IDVs). + + Args: + ordering: Server-side sort. Allowed: ``vehicle_obligations``, + ``latest_award_date``. Prefix with ``-`` for descending. + """ params: dict[str, Any] = {"page": page, "limit": min(limit, 100)} if shape is None: shape = ShapeConfig.VEHICLES_MINIMAL + else: + self._warn_deprecated_vehicle_shape(shape) if shape: params["shape"] = shape if flat: @@ -1473,6 +1517,8 @@ def list_vehicles( if search: params["search"] = search + if ordering: + params["ordering"] = ordering data = self._get("/api/vehicles/", params) @@ -1504,6 +1550,8 @@ def get_vehicle( if shape is None: shape = ShapeConfig.VEHICLES_COMPREHENSIVE + else: + self._warn_deprecated_vehicle_shape(shape) if shape: params["shape"] = shape if flat: @@ -1560,6 +1608,54 @@ def list_vehicle_awardees( results=results, ) + def list_vehicle_orders( + self, + uuid: str, + page: int = 1, + limit: int = 25, + shape: str | None = None, + flat: bool = False, + flat_lists: bool = False, + joiner: str = ".", + ordering: str | None = None, + ) -> PaginatedResponse: + """List task orders under a Vehicle's IDVs (``/api/vehicles/{uuid}/orders/``). + + Args: + ordering: Server-side sort. Allowed: ``award_date`` (default), + ``obligated``, ``total_contract_value``. Prefix with ``-`` for + descending. + """ + params: dict[str, Any] = {"page": page, "limit": min(limit, 100)} + + if shape is None: + shape = ShapeConfig.VEHICLE_ORDERS_MINIMAL + if shape: + params["shape"] = shape + if flat: + params["flat"] = "true" + if joiner: + params["joiner"] = joiner + if flat_lists: + params["flat_lists"] = "true" + + if ordering: + params["ordering"] = ordering + + data = self._get(f"/api/vehicles/{uuid}/orders/", params) + + results = [ + self._parse_response_with_shape(order, shape, Contract, flat, flat_lists, joiner=joiner) + for order in data["results"] + ] + + return PaginatedResponse( + count=data["count"], + next=data.get("next"), + previous=data.get("previous"), + results=results, + ) + # Business Types endpoints def list_business_types(self, page: int = 1, limit: int = 25) -> PaginatedResponse: """List business types""" diff --git a/tango/models.py b/tango/models.py index 3cab2c0..9137199 100644 --- a/tango/models.py +++ b/tango/models.py @@ -413,6 +413,12 @@ class Vehicle: uuid: str solicitation_identifier: str agency_id: str + is_synthetic_solicitation: bool | None = None + program_acronym: str | None = None + description: str | None = None + idv_count: int | None = None + total_obligated: Decimal | None = None + latest_award_date: date | None = None solicitation_title: str | None = None solicitation_date: date | None = None award_date: date | None = None @@ -420,6 +426,24 @@ class Vehicle: fiscal_year: int | None = None +@dataclass +class VehicleMetrics: + """Schema definition for the Vehicle `metrics(*)` expansion (not used for instances)""" + + avg_offers_received: float | None = None + award_concentration_hhi: float | None = None + order_concentration_hhi: float | None = None + competed_rate: float | None = None + using_agency_count: int | None = None + avg_order_value: float | None = None + max_order_value: float | None = None + top_recipient_share: float | None = None + recent_obligations_24mo: float | None = None + recent_orders_24mo: int | None = None + days_since_last_order: int | None = None + obligation_to_ceiling_ratio: float | None = None + + @dataclass class Entity: """Schema definition for Entity (not used for instances)""" @@ -690,21 +714,34 @@ class ShapeConfig: # Default for list_vehicles() VEHICLES_MINIMAL: Final = ( - "uuid,solicitation_identifier,organization_id,awardee_count,order_count," - "vehicle_obligations,vehicle_contracts_value,solicitation_title,solicitation_date" + "uuid,solicitation_identifier,is_synthetic_solicitation,program_acronym," + "organization_id,organization,vehicle_type,description," + "idv_count,awardee_count,order_count,total_obligated," + "vehicle_obligations,vehicle_contracts_value,latest_award_date," + "solicitation_title,solicitation_date" ) # Default for get_vehicle() VEHICLES_COMPREHENSIVE: Final = ( - "uuid,solicitation_identifier,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(*)" + "uuid,solicitation_identifier,is_synthetic_solicitation,agency_id,program_acronym," + "organization_id,organization,vehicle_type,who_can_use," + "solicitation_title,solicitation_description,solicitation_date,opportunity_id," + "naics_code,psc_code,set_aside," + "fiscal_year,award_date,latest_award_date,last_date_to_order," + "description,idv_count,awardee_count,order_count,total_obligated," + "vehicle_obligations,vehicle_contracts_value," + "type_of_idc,contract_type,metrics(*)" ) # Default for list_vehicle_awardees() VEHICLE_AWARDEES_MINIMAL: Final = "uuid,key,piid,award_date,title,order_count,idv_obligations,idv_contracts_value,recipient(display_name,uei)" + # Default for list_vehicle_orders() + VEHICLE_ORDERS_MINIMAL: Final = ( + "key,piid,award_date,obligated,total_contract_value,description," + "recipient(display_name,uei)" + ) + # Default for list_organizations() ORGANIZATIONS_MINIMAL: Final = "key,fh_key,name,level,type,short_name" diff --git a/tango/shapes/explicit_schemas.py b/tango/shapes/explicit_schemas.py index 3833cb6..3efe9d1 100644 --- a/tango/shapes/explicit_schemas.py +++ b/tango/shapes/explicit_schemas.py @@ -838,6 +838,45 @@ } +# Vehicles expose a "metrics(...)" expansion bundling computed metrics. +VEHICLE_METRICS_SCHEMA: dict[str, FieldSchema] = { + "avg_offers_received": FieldSchema( + name="avg_offers_received", type=float, is_optional=True, is_list=False + ), + "award_concentration_hhi": FieldSchema( + name="award_concentration_hhi", type=float, is_optional=True, is_list=False + ), + "order_concentration_hhi": FieldSchema( + name="order_concentration_hhi", type=float, is_optional=True, is_list=False + ), + "competed_rate": FieldSchema(name="competed_rate", type=float, is_optional=True, is_list=False), + "using_agency_count": FieldSchema( + name="using_agency_count", type=int, is_optional=True, is_list=False + ), + "avg_order_value": FieldSchema( + name="avg_order_value", type=float, is_optional=True, is_list=False + ), + "max_order_value": FieldSchema( + name="max_order_value", type=float, is_optional=True, is_list=False + ), + "top_recipient_share": FieldSchema( + name="top_recipient_share", type=float, is_optional=True, is_list=False + ), + "recent_obligations_24mo": FieldSchema( + name="recent_obligations_24mo", type=float, is_optional=True, is_list=False + ), + "recent_orders_24mo": FieldSchema( + name="recent_orders_24mo", type=int, is_optional=True, is_list=False + ), + "days_since_last_order": FieldSchema( + name="days_since_last_order", type=int, is_optional=True, is_list=False + ), + "obligation_to_ceiling_ratio": FieldSchema( + name="obligation_to_ceiling_ratio", type=float, is_optional=True, is_list=False + ), +} + + # IDV schema (used for `/api/idvs/`, and also by Vehicles awardees shaping). IDV_SCHEMA: dict[str, FieldSchema] = { # Identifiers @@ -970,26 +1009,49 @@ "solicitation_identifier": FieldSchema( name="solicitation_identifier", type=str, is_optional=False, is_list=False ), + "is_synthetic_solicitation": FieldSchema( + name="is_synthetic_solicitation", type=bool, is_optional=True, is_list=False + ), "agency_id": FieldSchema(name="agency_id", type=str, is_optional=False, is_list=False), "organization_id": FieldSchema( name="organization_id", type=str, is_optional=True, is_list=False ), + # Live awarding-org snapshot. Returned as a flat dict with keys: + # `organization_id`, `office_code`, `office_name`, `agency_code`, + # `agency_name`, `department_code`, `department_name`. Selected as a leaf + # token (`shape=...,organization`) — not as a sub-selectable expansion. + "organization": FieldSchema( + name="organization", type=dict, is_optional=True, is_list=False + ), # Choice fields are returned as {code, description} objects. "vehicle_type": FieldSchema(name="vehicle_type", type=dict, is_optional=True, is_list=False), + "program_acronym": FieldSchema( + name="program_acronym", type=str, is_optional=True, is_list=False + ), "who_can_use": FieldSchema(name="who_can_use", type=dict, is_optional=True, is_list=False), "type_of_idc": FieldSchema(name="type_of_idc", type=dict, is_optional=True, is_list=False), "contract_type": FieldSchema(name="contract_type", type=dict, is_optional=True, is_list=False), + # Deprecated: recomputed from IDVs at request time. Upstream sends `Deprecation: true`. "agency_details": FieldSchema( name="agency_details", type=dict, is_optional=True, is_list=False ), + "description": FieldSchema(name="description", type=str, is_optional=True, is_list=False), "descriptions": FieldSchema(name="descriptions", type=str, is_optional=True, is_list=True), "fiscal_year": FieldSchema(name="fiscal_year", type=int, is_optional=True, is_list=False), "award_date": FieldSchema(name="award_date", type=date, is_optional=True, is_list=False), + "latest_award_date": FieldSchema( + name="latest_award_date", type=date, is_optional=True, is_list=False + ), "last_date_to_order": FieldSchema( name="last_date_to_order", type=date, is_optional=True, is_list=False ), + # Denormalized rollups + "idv_count": FieldSchema(name="idv_count", type=int, is_optional=True, is_list=False), "awardee_count": FieldSchema(name="awardee_count", type=int, is_optional=True, is_list=False), "order_count": FieldSchema(name="order_count", type=int, is_optional=True, is_list=False), + "total_obligated": FieldSchema( + name="total_obligated", type=Decimal, is_optional=True, is_list=False + ), "vehicle_obligations": FieldSchema( name="vehicle_obligations", type=Decimal, is_optional=True, is_list=False ), @@ -1006,6 +1068,7 @@ "solicitation_date": FieldSchema( name="solicitation_date", type=date, is_optional=True, is_list=False ), + "opportunity_id": FieldSchema(name="opportunity_id", type=str, is_optional=True, is_list=False), "naics_code": FieldSchema(name="naics_code", type=int, is_optional=True, is_list=False), "psc_code": FieldSchema(name="psc_code", type=str, is_optional=True, is_list=False), "set_aside": FieldSchema(name="set_aside", type=str, is_optional=True, is_list=False), @@ -1013,6 +1076,14 @@ "awardees": FieldSchema( name="awardees", type=dict, is_optional=True, is_list=True, nested_model="IDV" ), + "metrics": FieldSchema( + name="metrics", + type=dict, + is_optional=True, + is_list=False, + nested_model="VehicleMetrics", + ), + # Deprecated expansions (upstream sends `Deprecation: true`). "opportunity": FieldSchema( name="opportunity", type=dict, is_optional=True, is_list=False, nested_model="Opportunity" ), @@ -1026,6 +1097,14 @@ } +# Top-level vehicle shape fields that upstream marks deprecated (the API sends +# `Deprecation: true` and recomputes them from IDVs at request time). The client +# emits a `DeprecationWarning` when callers explicitly include these in `shape`. +DEPRECATED_VEHICLE_SHAPE_FIELDS: frozenset[str] = frozenset( + {"agency_details", "competition_details", "opportunity"} +) + + # Organization (agencies hierarchy) ORGANIZATION_SCHEMA: dict[str, FieldSchema] = { "key": FieldSchema(name="key", type=str, is_optional=True, is_list=False), @@ -1135,18 +1214,10 @@ # IT Dashboard Investment ITDASHBOARD_INVESTMENT_SCHEMA: dict[str, FieldSchema] = { "uii": FieldSchema(name="uii", type=str, is_optional=False, is_list=False), - "agency_code": FieldSchema( - name="agency_code", type=int, is_optional=True, is_list=False - ), - "agency_name": FieldSchema( - name="agency_name", type=str, is_optional=True, is_list=False - ), - "bureau_code": FieldSchema( - name="bureau_code", type=int, is_optional=True, is_list=False - ), - "bureau_name": FieldSchema( - name="bureau_name", type=str, is_optional=True, is_list=False - ), + "agency_code": FieldSchema(name="agency_code", type=int, is_optional=True, is_list=False), + "agency_name": FieldSchema(name="agency_name", type=str, is_optional=True, is_list=False), + "bureau_code": FieldSchema(name="bureau_code", type=int, is_optional=True, is_list=False), + "bureau_name": FieldSchema(name="bureau_name", type=str, is_optional=True, is_list=False), "investment_title": FieldSchema( name="investment_title", type=str, is_optional=True, is_list=False ), @@ -1167,15 +1238,9 @@ # Modeled as opaque dict/list since their inner shapes are dynamic. "funding": FieldSchema(name="funding", type=dict, is_optional=True, is_list=False), "details": FieldSchema(name="details", type=dict, is_optional=True, is_list=False), - "cio_evaluation": FieldSchema( - name="cio_evaluation", type=list, is_optional=True, is_list=True - ), - "contracts": FieldSchema( - name="contracts", type=list, is_optional=True, is_list=True - ), - "projects": FieldSchema( - name="projects", type=list, is_optional=True, is_list=True - ), + "cio_evaluation": FieldSchema(name="cio_evaluation", type=list, is_optional=True, is_list=True), + "contracts": FieldSchema(name="contracts", type=list, is_optional=True, is_list=True), + "projects": FieldSchema(name="projects", type=list, is_optional=True, is_list=True), "cost_pools_towers": FieldSchema( name="cost_pools_towers", type=list, is_optional=True, is_list=True ), @@ -1225,6 +1290,7 @@ "Vehicle": VEHICLE_SCHEMA, "IDV": IDV_SCHEMA, "VehicleCompetitionDetails": VEHICLE_COMPETITION_DETAILS_SCHEMA, + "VehicleMetrics": VEHICLE_METRICS_SCHEMA, # Nested schemas for Grant fields "CFDANumber": CFDA_NUMBER_SCHEMA, "CodeDescription": CODE_DESCRIPTION_SCHEMA, diff --git a/tests/cassettes/TestVehiclesIntegration.test_get_vehicle_supports_joiner_and_flat_lists b/tests/cassettes/TestVehiclesIntegration.test_get_vehicle_supports_joiner_and_flat_lists index 4c516e8..56ba2da 100644 --- a/tests/cassettes/TestVehiclesIntegration.test_get_vehicle_supports_joiner_and_flat_lists +++ b/tests/cassettes/TestVehiclesIntegration.test_get_vehicle_supports_joiner_and_flat_lists @@ -13,24 +13,29 @@ interactions: user-agent: - python-httpx/0.28.1 method: GET - uri: https://tango.makegov.com/api/vehicles/?page=1&limit=1&shape=uuid%2Csolicitation_identifier%2Corganization_id%2Cawardee_count%2Corder_count%2Cvehicle_obligations%2Cvehicle_contracts_value%2Csolicitation_title%2Csolicitation_date + uri: https://tango.makegov.com/api/vehicles/?page=1&limit=1&shape=uuid%2Csolicitation_identifier%2Cis_synthetic_solicitation%2Cprogram_acronym%2Corganization_id%2Corganization%2Cvehicle_type%2Cdescription%2Cidv_count%2Cawardee_count%2Corder_count%2Ctotal_obligated%2Cvehicle_obligations%2Cvehicle_contracts_value%2Clatest_award_date%2Csolicitation_title%2Csolicitation_date response: body: - string: '{"count":5874,"next":"https://tango.makegov.com/api/vehicles/?limit=1&page=2&shape=uuid%2Csolicitation_identifier%2Corganization_id%2Cawardee_count%2Corder_count%2Cvehicle_obligations%2Cvehicle_contracts_value%2Csolicitation_title%2Csolicitation_date","previous":null,"results":[{"uuid":"84d76669-61d8-5938-83fb-2d6f8a6c85b7","solicitation_identifier":"0","organization_id":"f6c88e61-3d34-5685-a5ab-670858289883","awardee_count":38,"order_count":65,"vehicle_obligations":8876449.66,"vehicle_contracts_value":9688796.56,"solicitation_title":"X1LZ--618-20-2-6190-0007 - - Service - Minneapolis VA CRRC Parking, 33 Parking Spaces - MPLS","solicitation_date":"2020-03-02"}]}' + string: '{"count":20452,"next":"https://tango.makegov.com/api/vehicles/?limit=1&page=2&shape=uuid%2Csolicitation_identifier%2Cis_synthetic_solicitation%2Cprogram_acronym%2Corganization_id%2Corganization%2Cvehicle_type%2Cdescription%2Cidv_count%2Cawardee_count%2Corder_count%2Ctotal_obligated%2Cvehicle_obligations%2Cvehicle_contracts_value%2Clatest_award_date%2Csolicitation_title%2Csolicitation_date","previous":null,"results":[{"awardee_count":2,"description":["CATTLE + AND SWINE BACKTAG CEMENT","CATTLE AND SWINE BACKTAG CEMENT TO BE DELIVERED + ON AN IDIQ BASIS TO KS, MO."],"idv_count":2,"is_synthetic_solicitation":false,"latest_award_date":"2006-06-01","order_count":25,"organization":{"organization_id":"767bb2b0-239a-5bb4-8f3c-16d0a418fb71","office_code":"126395","office_name":"MRPBS + MINNEAPOLIS MN","agency_code":"12K3","agency_name":"ANIMAL AND PLANT HEALTH + INSPECTION SERVICE","department_code":"012","department_name":"AGRICULTURE, + DEPARTMENT OF"},"organization_id":"767bb2b0-239a-5bb4-8f3c-16d0a418fb71","program_acronym":"APHIS + VS","solicitation_date":null,"solicitation_identifier":"002-M-APHIS-06","solicitation_title":null,"total_obligated":5388564.69,"uuid":"8597845d-2a4c-5acd-99ff-1f5fe348501e","vehicle_contracts_value":5116733.97,"vehicle_obligations":5388564.6899999995,"vehicle_type":{"code":"B","description":"IDC"}}]}' headers: CF-RAY: - - 9d71a79d1e69ace2-MSP + - 9f73b81ebb963d59-JAX Connection: - keep-alive Content-Type: - application/json Date: - - Wed, 04 Mar 2026 14:43:42 GMT + - Wed, 06 May 2026 00:02:59 GMT Nel: - '{"report_to":"cf-nel","success_fraction":0.0,"max_age":604800}' Report-To: - - '{"group":"cf-nel","max_age":604800,"endpoints":[{"url":"https://a.nel.cloudflare.com/report/v4?s=foISsWd3o4Crwk58iKLLuBVkQFtC11yIJkhkCK4vC%2F0w4I28Kbk4j%2FWdF2Z4gVU3QAwqVjUkR9L3KC6Sa4dIYTFIEYgsuvxpb5FBysskgBHHwX4lRUqueUSX"}]}' + - '{"group":"cf-nel","max_age":604800,"endpoints":[{"url":"https://a.nel.cloudflare.com/report/v4?s=nlkDo84xSKlQqOxVMCULQpSFUr%2ByvNqk5clNAiAUG3P3v9GZj4sWYIijGnArPlv%2BaO9z%2B637EZfDn5VLkMykl6392hG%2BWNMYaI9%2B0cXLrEgfbcn12luHzYHEcVOIk2By9FZVYoJbO6CEhCeoeNzS"}]}' Server: - cloudflare Transfer-Encoding: @@ -40,7 +45,7 @@ interactions: cf-cache-status: - DYNAMIC content-length: - - '668' + - '1333' cross-origin-opener-policy: - same-origin referrer-policy: @@ -50,27 +55,29 @@ interactions: x-content-type-options: - nosniff x-execution-time: - - 0.021s + - 0.024s x-frame-options: - DENY x-ratelimit-burst-limit: - '1000' x-ratelimit-burst-remaining: - - '910' + - '986' x-ratelimit-burst-reset: - - '6' + - '29' x-ratelimit-daily-limit: - '2000000' x-ratelimit-daily-remaining: - - '1999670' + - '1999986' x-ratelimit-daily-reset: - - '84966' + - '86220' x-ratelimit-limit: - '1000' x-ratelimit-remaining: - - '910' + - '986' x-ratelimit-reset: - - '6' + - '29' + x-results-counttype: + - exact status: code: 200 message: OK @@ -88,24 +95,26 @@ interactions: user-agent: - python-httpx/0.28.1 method: GET - uri: https://tango.makegov.com/api/vehicles/84d76669-61d8-5938-83fb-2d6f8a6c85b7/?shape=uuid%2Copportunity%28title%29&flat=true&joiner=__&flat_lists=true + uri: https://tango.makegov.com/api/vehicles/8597845d-2a4c-5acd-99ff-1f5fe348501e/?shape=uuid%2Corganization&flat=true&joiner=__&flat_lists=true response: body: - string: '{"uuid":"84d76669-61d8-5938-83fb-2d6f8a6c85b7","opportunity__title":"X1LZ--618-20-2-6190-0007 - - Service - Minneapolis VA CRRC Parking, 33 Parking Spaces - MPLS"}' + string: '{"organization__organization_id":"767bb2b0-239a-5bb4-8f3c-16d0a418fb71","organization__office_code":"126395","organization__office_name":"MRPBS + MINNEAPOLIS MN","organization__agency_code":"12K3","organization__agency_name":"ANIMAL + AND PLANT HEALTH INSPECTION SERVICE","organization__department_code":"012","organization__department_name":"AGRICULTURE, + DEPARTMENT OF","uuid":"8597845d-2a4c-5acd-99ff-1f5fe348501e"}' headers: CF-RAY: - - 9d71a79de845ace2-MSP + - 9f73b81f5ca93d59-JAX Connection: - keep-alive Content-Type: - application/json Date: - - Wed, 04 Mar 2026 14:43:42 GMT + - Wed, 06 May 2026 00:02:59 GMT Nel: - '{"report_to":"cf-nel","success_fraction":0.0,"max_age":604800}' Report-To: - - '{"group":"cf-nel","max_age":604800,"endpoints":[{"url":"https://a.nel.cloudflare.com/report/v4?s=fTIzuP10eNwQ3RQvXSnqPSyKJ%2F0GkCtQtRiwPTkcg7j62kkVcPa6owX9t2k8dFjcoroz4y6rj8b251WYX0UtASQXH3CIXIV2MxlJ6CzJ9%2FUD%2FcYSU6Ckn1vO"}]}' + - '{"group":"cf-nel","max_age":604800,"endpoints":[{"url":"https://a.nel.cloudflare.com/report/v4?s=CqJJ85Cgv80%2Fz2c7k61dXKeYrVFFz8RUxhtcsY3j86jFwjPcYo7xcPBjBH5cyohJxE%2FWeS%2Bdy2YWh1tWkStjis8jta9f%2BGiiyKyRqvQ3Zb3kzqFQwTGjXnTMPTW%2FNrzQ8shECys8DGoRPOZnWOab"}]}' Server: - cloudflare Transfer-Encoding: @@ -115,7 +124,7 @@ interactions: cf-cache-status: - DYNAMIC content-length: - - '161' + - '413' cross-origin-opener-policy: - same-origin referrer-policy: @@ -125,27 +134,27 @@ interactions: x-content-type-options: - nosniff x-execution-time: - - 0.032s + - 0.016s x-frame-options: - DENY x-ratelimit-burst-limit: - '1000' x-ratelimit-burst-remaining: - - '909' + - '985' x-ratelimit-burst-reset: - - '6' + - '29' x-ratelimit-daily-limit: - '2000000' x-ratelimit-daily-remaining: - - '1999669' + - '1999985' x-ratelimit-daily-reset: - - '84966' + - '86220' x-ratelimit-limit: - '1000' x-ratelimit-remaining: - - '909' + - '985' x-ratelimit-reset: - - '6' + - '29' status: code: 200 message: OK diff --git a/tests/cassettes/TestVehiclesIntegration.test_get_vehicle_with_metrics_expansion b/tests/cassettes/TestVehiclesIntegration.test_get_vehicle_with_metrics_expansion new file mode 100644 index 0000000..f06f0e0 --- /dev/null +++ b/tests/cassettes/TestVehiclesIntegration.test_get_vehicle_with_metrics_expansion @@ -0,0 +1,158 @@ +interactions: +- request: + body: '' + headers: + accept: + - '*/*' + accept-encoding: + - gzip, deflate + connection: + - keep-alive + host: + - tango.makegov.com + user-agent: + - python-httpx/0.28.1 + method: GET + uri: https://tango.makegov.com/api/vehicles/?page=1&limit=1&shape=uuid%2Csolicitation_identifier%2Cis_synthetic_solicitation%2Cprogram_acronym%2Corganization_id%2Corganization%2Cvehicle_type%2Cdescription%2Cidv_count%2Cawardee_count%2Corder_count%2Ctotal_obligated%2Cvehicle_obligations%2Cvehicle_contracts_value%2Clatest_award_date%2Csolicitation_title%2Csolicitation_date + response: + body: + string: '{"count":20452,"next":"https://tango.makegov.com/api/vehicles/?limit=1&page=2&shape=uuid%2Csolicitation_identifier%2Cis_synthetic_solicitation%2Cprogram_acronym%2Corganization_id%2Corganization%2Cvehicle_type%2Cdescription%2Cidv_count%2Cawardee_count%2Corder_count%2Ctotal_obligated%2Cvehicle_obligations%2Cvehicle_contracts_value%2Clatest_award_date%2Csolicitation_title%2Csolicitation_date","previous":null,"results":[{"awardee_count":2,"description":["CATTLE + AND SWINE BACKTAG CEMENT","CATTLE AND SWINE BACKTAG CEMENT TO BE DELIVERED + ON AN IDIQ BASIS TO KS, MO."],"idv_count":2,"is_synthetic_solicitation":false,"latest_award_date":"2006-06-01","order_count":25,"organization":{"organization_id":"767bb2b0-239a-5bb4-8f3c-16d0a418fb71","office_code":"126395","office_name":"MRPBS + MINNEAPOLIS MN","agency_code":"12K3","agency_name":"ANIMAL AND PLANT HEALTH + INSPECTION SERVICE","department_code":"012","department_name":"AGRICULTURE, + DEPARTMENT OF"},"organization_id":"767bb2b0-239a-5bb4-8f3c-16d0a418fb71","program_acronym":"APHIS + VS","solicitation_date":null,"solicitation_identifier":"002-M-APHIS-06","solicitation_title":null,"total_obligated":5388564.69,"uuid":"8597845d-2a4c-5acd-99ff-1f5fe348501e","vehicle_contracts_value":5116733.97,"vehicle_obligations":5388564.6899999995,"vehicle_type":{"code":"B","description":"IDC"}}]}' + headers: + CF-RAY: + - 9f73b823692c60c7-JAX + Connection: + - keep-alive + Content-Type: + - application/json + Date: + - Wed, 06 May 2026 00:02:59 GMT + Nel: + - '{"report_to":"cf-nel","success_fraction":0.0,"max_age":604800}' + Report-To: + - '{"group":"cf-nel","max_age":604800,"endpoints":[{"url":"https://a.nel.cloudflare.com/report/v4?s=B6gMSdTzCoCyerbzAHIcVlqBefWD2yJM28l29mzuR4ye3cO5QBChXu%2Foof465c8FHkZe8jIIWNe9U9rJfvAMpvgPgzx8eLhyJYVdac5U3d9XyF0qKbxJt58%2FMsn51P4CcUG49wsHDkNwLfd7N8qo"}]}' + Server: + - cloudflare + Transfer-Encoding: + - chunked + allow: + - GET, HEAD, OPTIONS + cf-cache-status: + - DYNAMIC + content-length: + - '1333' + cross-origin-opener-policy: + - same-origin + referrer-policy: + - same-origin + vary: + - Accept, Cookie + x-content-type-options: + - nosniff + x-execution-time: + - 0.021s + x-frame-options: + - DENY + x-ratelimit-burst-limit: + - '1000' + x-ratelimit-burst-remaining: + - '981' + x-ratelimit-burst-reset: + - '28' + x-ratelimit-daily-limit: + - '2000000' + x-ratelimit-daily-remaining: + - '1999981' + x-ratelimit-daily-reset: + - '86220' + x-ratelimit-limit: + - '1000' + x-ratelimit-remaining: + - '981' + x-ratelimit-reset: + - '28' + x-results-counttype: + - exact + status: + code: 200 + message: OK +- request: + body: '' + headers: + accept: + - '*/*' + accept-encoding: + - gzip, deflate + connection: + - keep-alive + host: + - tango.makegov.com + user-agent: + - python-httpx/0.28.1 + method: GET + uri: https://tango.makegov.com/api/vehicles/8597845d-2a4c-5acd-99ff-1f5fe348501e/?shape=uuid%2Cmetrics%28%2A%29 + response: + body: + string: '{"metrics":{"avg_offers_received":1.5,"award_concentration_hhi":6058.0,"order_concentration_hhi":5200.0,"competed_rate":0.88,"using_agency_count":1,"avg_order_value":215542.58759999997,"max_order_value":602601.6,"top_recipient_share":72.99,"recent_obligations_24mo":0.0,"recent_orders_24mo":0,"days_since_last_order":5710,"obligation_to_ceiling_ratio":1.053125826277812},"uuid":"8597845d-2a4c-5acd-99ff-1f5fe348501e"}' + headers: + CF-RAY: + - 9f73b823da9360c7-JAX + Connection: + - keep-alive + Content-Type: + - application/json + Date: + - Wed, 06 May 2026 00:02:59 GMT + Nel: + - '{"report_to":"cf-nel","success_fraction":0.0,"max_age":604800}' + Report-To: + - '{"group":"cf-nel","max_age":604800,"endpoints":[{"url":"https://a.nel.cloudflare.com/report/v4?s=5i%2Bk54VkK%2FXROY20rDQl9Rsx8mWgHA2Cn8jk3aUms2rC%2FpUJW8g4ecY5n8OZlTXhCF%2B5a0kpjUoAXs7btqOqBrmtTDnAaKNTjRYKpz72at%2Bbcu8K%2BCcopKM7zRf7KdNL9q9hPXB01yre8vUVrCJ0"}]}' + Server: + - cloudflare + Transfer-Encoding: + - chunked + allow: + - GET, HEAD, OPTIONS + cf-cache-status: + - DYNAMIC + content-length: + - '417' + cross-origin-opener-policy: + - same-origin + referrer-policy: + - same-origin + vary: + - Accept, Cookie + x-content-type-options: + - nosniff + x-execution-time: + - 0.016s + x-frame-options: + - DENY + x-ratelimit-burst-limit: + - '1000' + x-ratelimit-burst-remaining: + - '980' + x-ratelimit-burst-reset: + - '28' + x-ratelimit-daily-limit: + - '2000000' + x-ratelimit-daily-remaining: + - '1999980' + x-ratelimit-daily-reset: + - '86220' + x-ratelimit-limit: + - '1000' + x-ratelimit-remaining: + - '980' + x-ratelimit-reset: + - '28' + status: + code: 200 + message: OK +version: 1 diff --git a/tests/cassettes/TestVehiclesIntegration.test_list_vehicle_awardees_uses_default_shape b/tests/cassettes/TestVehiclesIntegration.test_list_vehicle_awardees_uses_default_shape index c8e7417..bdffcb9 100644 --- a/tests/cassettes/TestVehiclesIntegration.test_list_vehicle_awardees_uses_default_shape +++ b/tests/cassettes/TestVehiclesIntegration.test_list_vehicle_awardees_uses_default_shape @@ -13,24 +13,29 @@ interactions: user-agent: - python-httpx/0.28.1 method: GET - uri: https://tango.makegov.com/api/vehicles/?page=1&limit=1&shape=uuid%2Csolicitation_identifier%2Corganization_id%2Cawardee_count%2Corder_count%2Cvehicle_obligations%2Cvehicle_contracts_value%2Csolicitation_title%2Csolicitation_date + uri: https://tango.makegov.com/api/vehicles/?page=1&limit=1&shape=uuid%2Csolicitation_identifier%2Cis_synthetic_solicitation%2Cprogram_acronym%2Corganization_id%2Corganization%2Cvehicle_type%2Cdescription%2Cidv_count%2Cawardee_count%2Corder_count%2Ctotal_obligated%2Cvehicle_obligations%2Cvehicle_contracts_value%2Clatest_award_date%2Csolicitation_title%2Csolicitation_date response: body: - string: '{"count":5874,"next":"https://tango.makegov.com/api/vehicles/?limit=1&page=2&shape=uuid%2Csolicitation_identifier%2Corganization_id%2Cawardee_count%2Corder_count%2Cvehicle_obligations%2Cvehicle_contracts_value%2Csolicitation_title%2Csolicitation_date","previous":null,"results":[{"uuid":"84d76669-61d8-5938-83fb-2d6f8a6c85b7","solicitation_identifier":"0","organization_id":"f6c88e61-3d34-5685-a5ab-670858289883","awardee_count":38,"order_count":65,"vehicle_obligations":8876449.66,"vehicle_contracts_value":9688796.56,"solicitation_title":"X1LZ--618-20-2-6190-0007 - - Service - Minneapolis VA CRRC Parking, 33 Parking Spaces - MPLS","solicitation_date":"2020-03-02"}]}' + string: '{"count":20452,"next":"https://tango.makegov.com/api/vehicles/?limit=1&page=2&shape=uuid%2Csolicitation_identifier%2Cis_synthetic_solicitation%2Cprogram_acronym%2Corganization_id%2Corganization%2Cvehicle_type%2Cdescription%2Cidv_count%2Cawardee_count%2Corder_count%2Ctotal_obligated%2Cvehicle_obligations%2Cvehicle_contracts_value%2Clatest_award_date%2Csolicitation_title%2Csolicitation_date","previous":null,"results":[{"awardee_count":2,"description":["CATTLE + AND SWINE BACKTAG CEMENT","CATTLE AND SWINE BACKTAG CEMENT TO BE DELIVERED + ON AN IDIQ BASIS TO KS, MO."],"idv_count":2,"is_synthetic_solicitation":false,"latest_award_date":"2006-06-01","order_count":25,"organization":{"organization_id":"767bb2b0-239a-5bb4-8f3c-16d0a418fb71","office_code":"126395","office_name":"MRPBS + MINNEAPOLIS MN","agency_code":"12K3","agency_name":"ANIMAL AND PLANT HEALTH + INSPECTION SERVICE","department_code":"012","department_name":"AGRICULTURE, + DEPARTMENT OF"},"organization_id":"767bb2b0-239a-5bb4-8f3c-16d0a418fb71","program_acronym":"APHIS + VS","solicitation_date":null,"solicitation_identifier":"002-M-APHIS-06","solicitation_title":null,"total_obligated":5388564.69,"uuid":"8597845d-2a4c-5acd-99ff-1f5fe348501e","vehicle_contracts_value":5116733.97,"vehicle_obligations":5388564.6899999995,"vehicle_type":{"code":"B","description":"IDC"}}]}' headers: CF-RAY: - - 9d71a79f4d4ba1c9-MSP + - 9f73b82028e92733-JAX Connection: - keep-alive Content-Type: - application/json Date: - - Wed, 04 Mar 2026 14:43:42 GMT + - Wed, 06 May 2026 00:02:59 GMT Nel: - '{"report_to":"cf-nel","success_fraction":0.0,"max_age":604800}' Report-To: - - '{"group":"cf-nel","max_age":604800,"endpoints":[{"url":"https://a.nel.cloudflare.com/report/v4?s=g0j44m3AvOYMt1ZDTnMMFZ2MIbXGngySfFBMATk3ynlDhde23D0RJVscwUPb6%2FacawtgKkwd%2Bq2ufXW62HT1tqPmdW1YWEDTq8jkHS7HBqy1vBy1fFH36z7q"}]}' + - '{"group":"cf-nel","max_age":604800,"endpoints":[{"url":"https://a.nel.cloudflare.com/report/v4?s=pb6L38IhrE8slcKjDmpMOoPMm4XFe4ByVQdtJXbaKHCLmJmZg4mj8VaQO%2F1v7gOmIbL9H3TNqGXF7WPcULg3j8haT%2B%2FMMkOVPve4NgEzKZxAJL8XFw9Oklv86aTGvrYJyH8zq39%2BOrnMHDI0S5g0"}]}' Server: - cloudflare Transfer-Encoding: @@ -40,7 +45,7 @@ interactions: cf-cache-status: - DYNAMIC content-length: - - '668' + - '1333' cross-origin-opener-policy: - same-origin referrer-policy: @@ -56,21 +61,23 @@ interactions: x-ratelimit-burst-limit: - '1000' x-ratelimit-burst-remaining: - - '908' + - '984' x-ratelimit-burst-reset: - - '6' + - '29' x-ratelimit-daily-limit: - '2000000' x-ratelimit-daily-remaining: - - '1999668' + - '1999984' x-ratelimit-daily-reset: - - '84966' + - '86220' x-ratelimit-limit: - '1000' x-ratelimit-remaining: - - '908' + - '984' x-ratelimit-reset: - - '6' + - '29' + x-results-counttype: + - exact status: code: 200 message: OK @@ -88,41 +95,26 @@ interactions: user-agent: - python-httpx/0.28.1 method: GET - uri: https://tango.makegov.com/api/vehicles/84d76669-61d8-5938-83fb-2d6f8a6c85b7/awardees/?page=1&limit=10&shape=uuid%2Ckey%2Cpiid%2Caward_date%2Ctitle%2Corder_count%2Cidv_obligations%2Cidv_contracts_value%2Crecipient%28display_name%2Cuei%29 + uri: https://tango.makegov.com/api/vehicles/8597845d-2a4c-5acd-99ff-1f5fe348501e/awardees/?page=1&limit=10&shape=uuid%2Ckey%2Cpiid%2Caward_date%2Ctitle%2Corder_count%2Cidv_obligations%2Cidv_contracts_value%2Crecipient%28display_name%2Cuei%29 response: body: - string: '{"count":38,"next":"https://tango.makegov.com/api/vehicles/84d76669-61d8-5938-83fb-2d6f8a6c85b7/awardees/?limit=10&page=2&shape=uuid%2Ckey%2Cpiid%2Caward_date%2Ctitle%2Corder_count%2Cidv_obligations%2Cidv_contracts_value%2Crecipient%28display_name%2Cuei%29","previous":null,"results":[{"uuid":"d3eec2d2-f46d-5f3a-a2b3-da474202982d","key":"CONT_IDV_36C24925A0044_3600","piid":"36C24925A0044","award_date":"2025-02-26","title":null,"order_count":2,"idv_obligations":97177.1,"idv_contracts_value":97177.1,"recipient":{"uei":"HPNGJJKW7NZ3","display_name":"MIM - SOFTWARE INC"}},{"uuid":"6b35e54e-cdd7-547a-9912-4f32889904ee","key":"CONT_IDV_36C24923A0055_3600","piid":"36C24923A0055","award_date":"2023-09-21","title":"LEXINGTON - VAMC AMOS CONSIGNMENT","order_count":0,"idv_obligations":0.0,"idv_contracts_value":0.0,"recipient":{"uei":"G8XGKTUWPM14","display_name":"AMO - SALES AND SERVICE, INC."}},{"uuid":"4734026e-f902-5e4d-ae05-2625e9ee09b3","key":"CONT_IDV_36C10X23A0024_3600","piid":"36C10X23A0024","award_date":"2023-09-19","title":"HR - SUPPORT SERVICES","order_count":2,"idv_obligations":929714.8,"idv_contracts_value":931419.55,"recipient":{"uei":"WRHHNUK5YBN1","display_name":"RIVIDIUM - INC."}},{"uuid":"a2304733-ca28-5157-b800-ace63deb1f79","key":"CONT_IDV_36C24723A0033_3600","piid":"36C24723A0033","award_date":"2023-06-23","title":"CONSIGNMENT - INVENTORY","order_count":0,"idv_obligations":0.0,"idv_contracts_value":0.0,"recipient":{"uei":"KP5EVFMHUAN1","display_name":"ABBOTT - LABORATORIES INC."}},{"uuid":"8a721329-40f9-5ca3-858d-ed9a067371f2","key":"CONT_IDV_36C26022A0045_3600","piid":"36C26022A0045","award_date":"2022-09-12","title":"INTRA - OCULAR LENSE CONSIGNMENT AGREEMENT","order_count":0,"idv_obligations":0.0,"idv_contracts_value":0.0,"recipient":{"uei":"G8XGKTUWPM14","display_name":"AMO - SALES AND SERVICE, INC."}},{"uuid":"113dac46-3903-5889-9fc1-d4a58c01c591","key":"CONT_IDV_36C24122A0070_3600","piid":"36C24122A0070","award_date":"2022-03-17","title":"BLANKET - PURCHASE AGREEMENT FOR SUPPLIES NATIONWIDE VHA","order_count":0,"idv_obligations":0.0,"idv_contracts_value":0.0,"recipient":{"uei":"L3CLKHB2VE24","display_name":"Sage - Products, LLC"}},{"uuid":"040283f5-ed08-5434-9df4-907c7af62e4f","key":"CONT_IDV_36C25022A0009_3600","piid":"36C25022A0009","award_date":"2021-10-01","title":"COOK - MEDICAL LLC CONSIGNMENT - IMPLANTS","order_count":0,"idv_obligations":0.0,"idv_contracts_value":0.0,"recipient":{"uei":"GG39AE315NK5","display_name":"COOK - MEDICAL LLC"}},{"uuid":"7fe6ae99-081f-5be6-bd03-6a9bbf354980","key":"CONT_IDV_36C25021A0056_3600","piid":"36C25021A0056","award_date":"2021-03-23","title":null,"order_count":5,"idv_obligations":128048.14,"idv_contracts_value":164888.14,"recipient":{"uei":"STNDUK44ENE8","display_name":"POLYMEDCO - LLC"}},{"uuid":"3ab4321d-76a5-58c2-aaf6-85321ccd1bc1","key":"CONT_IDV_36C24821A0018_3600","piid":"36C24821A0018","award_date":"2021-03-17","title":"STERNAL - PLATES AND SCREWS","order_count":0,"idv_obligations":0.0,"idv_contracts_value":0.0,"recipient":{"uei":"JB1EA2TU8V88","display_name":"BIOMET - MICROFIXATION, LLC"}},{"uuid":"141b0632-e12b-5ac8-a862-869bc1bd9f87","key":"CONT_IDV_36C24821A0016_3600","piid":"36C24821A0016","award_date":"2021-02-15","title":"CONSIGNMENT - AGREEMENT - PERIPHERAL VASCULAR EMBOLIZATION PRODUCTS","order_count":0,"idv_obligations":0.0,"idv_contracts_value":0.0,"recipient":{"uei":"QLC9LYKADGX5","display_name":"PENUMBRA, - INC."}}]}' + string: '{"count":2,"next":null,"previous":null,"results":[{"award_date":"2006-06-01","idv_contracts_value":1183372.8,"idv_obligations":1455203.52,"key":"CONT_IDV_AG6395C060018_12K3","order_count":10,"piid":"AG6395C060018","recipient":{"uei":"GDVZPLNAKLL9","display_name":"STEVEN + INDUSTRIES INC"},"title":"CATTLE AND SWINE BACKTAG CEMENT","uuid":"af5f3527-b51b-55d8-9c6f-2d6aad936d31"},{"award_date":"2006-06-01","idv_contracts_value":3933361.17,"idv_obligations":3933361.17,"key":"CONT_IDV_AG6395C060019_12K3","order_count":15,"piid":"AG6395C060019","recipient":{"uei":"C1D7UA31LAK8","display_name":"M + J RUSCOE"},"title":"CATTLE AND SWINE BACKTAG CEMENT TO BE DELIVERED ON AN + IDIQ BASIS TO KS, MO.","uuid":"55cdcfa5-9b94-5aa5-8a8b-7a291d675e20"}]}' headers: CF-RAY: - - 9d71a7a00eb0a1c9-MSP + - 9f73b820ba5d2733-JAX Connection: - keep-alive Content-Type: - application/json Date: - - Wed, 04 Mar 2026 14:43:42 GMT + - Wed, 06 May 2026 00:02:59 GMT Nel: - '{"report_to":"cf-nel","success_fraction":0.0,"max_age":604800}' Report-To: - - '{"group":"cf-nel","max_age":604800,"endpoints":[{"url":"https://a.nel.cloudflare.com/report/v4?s=PA0MWV3V4YvOgApvlK%2B30Y0fxb8uTbbVuL9VqVukOJLOvEMUylvJhadJX%2BuzJ2P7b5Nd8l6H5tYqYB%2FRfPaJao1Je5g32SIOLm0i2Ohjsm7hv1lGzYI6qyl2"}]}' + - '{"group":"cf-nel","max_age":604800,"endpoints":[{"url":"https://a.nel.cloudflare.com/report/v4?s=Hhdv2%2B3QcWBaFBmrjpPppmB%2BWUA7Qs%2FTsc05%2BmgouFx8U5esHFgTLVxRQMHjK7htLjAT4aI7s2cu43j1IjRqN8P%2FhFHLKq4yMnqrZ%2B3zionLGPhJFQ%2FVbIiaNsVRBsuCRRHzED8Mq%2F%2FohxH5q62o"}]}' Server: - cloudflare Transfer-Encoding: @@ -132,7 +124,7 @@ interactions: cf-cache-status: - DYNAMIC content-length: - - '3418' + - '739' cross-origin-opener-policy: - same-origin referrer-policy: @@ -142,27 +134,27 @@ interactions: x-content-type-options: - nosniff x-execution-time: - - 0.087s + - 0.036s x-frame-options: - DENY x-ratelimit-burst-limit: - '1000' x-ratelimit-burst-remaining: - - '907' + - '983' x-ratelimit-burst-reset: - - '6' + - '29' x-ratelimit-daily-limit: - '2000000' x-ratelimit-daily-remaining: - - '1999667' + - '1999983' x-ratelimit-daily-reset: - - '84966' + - '86220' x-ratelimit-limit: - '1000' x-ratelimit-remaining: - - '907' + - '983' x-ratelimit-reset: - - '6' + - '29' status: code: 200 message: OK diff --git a/tests/cassettes/TestVehiclesIntegration.test_list_vehicle_orders_uses_default_shape b/tests/cassettes/TestVehiclesIntegration.test_list_vehicle_orders_uses_default_shape new file mode 100644 index 0000000..5bda308 --- /dev/null +++ b/tests/cassettes/TestVehiclesIntegration.test_list_vehicle_orders_uses_default_shape @@ -0,0 +1,184 @@ +interactions: +- request: + body: '' + headers: + accept: + - '*/*' + accept-encoding: + - gzip, deflate + connection: + - keep-alive + host: + - tango.makegov.com + user-agent: + - python-httpx/0.28.1 + method: GET + uri: https://tango.makegov.com/api/vehicles/?page=1&limit=1&shape=uuid%2Csolicitation_identifier%2Cis_synthetic_solicitation%2Cprogram_acronym%2Corganization_id%2Corganization%2Cvehicle_type%2Cdescription%2Cidv_count%2Cawardee_count%2Corder_count%2Ctotal_obligated%2Cvehicle_obligations%2Cvehicle_contracts_value%2Clatest_award_date%2Csolicitation_title%2Csolicitation_date + response: + body: + string: '{"count":20452,"next":"https://tango.makegov.com/api/vehicles/?limit=1&page=2&shape=uuid%2Csolicitation_identifier%2Cis_synthetic_solicitation%2Cprogram_acronym%2Corganization_id%2Corganization%2Cvehicle_type%2Cdescription%2Cidv_count%2Cawardee_count%2Corder_count%2Ctotal_obligated%2Cvehicle_obligations%2Cvehicle_contracts_value%2Clatest_award_date%2Csolicitation_title%2Csolicitation_date","previous":null,"results":[{"awardee_count":2,"description":["CATTLE + AND SWINE BACKTAG CEMENT","CATTLE AND SWINE BACKTAG CEMENT TO BE DELIVERED + ON AN IDIQ BASIS TO KS, MO."],"idv_count":2,"is_synthetic_solicitation":false,"latest_award_date":"2006-06-01","order_count":25,"organization":{"organization_id":"767bb2b0-239a-5bb4-8f3c-16d0a418fb71","office_code":"126395","office_name":"MRPBS + MINNEAPOLIS MN","agency_code":"12K3","agency_name":"ANIMAL AND PLANT HEALTH + INSPECTION SERVICE","department_code":"012","department_name":"AGRICULTURE, + DEPARTMENT OF"},"organization_id":"767bb2b0-239a-5bb4-8f3c-16d0a418fb71","program_acronym":"APHIS + VS","solicitation_date":null,"solicitation_identifier":"002-M-APHIS-06","solicitation_title":null,"total_obligated":5388564.69,"uuid":"8597845d-2a4c-5acd-99ff-1f5fe348501e","vehicle_contracts_value":5116733.97,"vehicle_obligations":5388564.6899999995,"vehicle_type":{"code":"B","description":"IDC"}}]}' + headers: + CF-RAY: + - 9f73b824ba669934-JAX + Connection: + - keep-alive + Content-Type: + - application/json + Date: + - Wed, 06 May 2026 00:03:00 GMT + Nel: + - '{"report_to":"cf-nel","success_fraction":0.0,"max_age":604800}' + Report-To: + - '{"group":"cf-nel","max_age":604800,"endpoints":[{"url":"https://a.nel.cloudflare.com/report/v4?s=AUn0qs38WwHOJZc2XMWR%2Bht6rOF8JGHTzt7H7Onn63UyHktmeiTCsvGECYxGJXUDVPZvvj%2FJkaol61cuC3dERnJJPBnV9ZzOkYSZzTB9cUsxcgF4Zbtq2gGEq0BiVAVqMTEb%2BC6U5WlFbqjvDxHC"}]}' + Server: + - cloudflare + Transfer-Encoding: + - chunked + allow: + - GET, HEAD, OPTIONS + cf-cache-status: + - DYNAMIC + content-length: + - '1333' + cross-origin-opener-policy: + - same-origin + referrer-policy: + - same-origin + vary: + - Accept, Cookie + x-content-type-options: + - nosniff + x-execution-time: + - 0.040s + x-frame-options: + - DENY + x-ratelimit-burst-limit: + - '1000' + x-ratelimit-burst-remaining: + - '979' + x-ratelimit-burst-reset: + - '28' + x-ratelimit-daily-limit: + - '2000000' + x-ratelimit-daily-remaining: + - '1999979' + x-ratelimit-daily-reset: + - '86219' + x-ratelimit-limit: + - '1000' + x-ratelimit-remaining: + - '979' + x-ratelimit-reset: + - '28' + x-results-counttype: + - exact + status: + code: 200 + message: OK +- request: + body: '' + headers: + accept: + - '*/*' + accept-encoding: + - gzip, deflate + connection: + - keep-alive + host: + - tango.makegov.com + user-agent: + - python-httpx/0.28.1 + method: GET + uri: https://tango.makegov.com/api/vehicles/8597845d-2a4c-5acd-99ff-1f5fe348501e/orders/?page=1&limit=10&shape=key%2Cpiid%2Caward_date%2Cobligated%2Ctotal_contract_value%2Cdescription%2Crecipient%28display_name%2Cuei%29 + response: + body: + string: '{"count":25,"next":"https://tango.makegov.com/api/vehicles/8597845d-2a4c-5acd-99ff-1f5fe348501e/orders/?limit=10&page=2&shape=key%2Cpiid%2Caward_date%2Cobligated%2Ctotal_contract_value%2Cdescription%2Crecipient%28display_name%2Cuei%29","previous":null,"results":[{"award_date":"2010-09-16","description":"DELIVERY + ORDER FOR BACKTAG CEMENT.","key":"CONT_AWD_AG6395D100682_12K3_AG6395C060019_12K3","obligated":399827.2,"piid":"AG6395D100682","recipient":{"uei":"C1D7UA31LAK8","display_name":"M + J RUSCOE"},"total_contract_value":399827.2},{"award_date":"2010-08-03","description":"BACKTAG + CEMENT IN TUBES","key":"CONT_AWD_AG6395D100569_12K3_AG6395C060019_12K3","obligated":236160.0,"piid":"AG6395D100569","recipient":{"uei":"C1D7UA31LAK8","display_name":"M + J RUSCOE"},"total_contract_value":236160.0},{"award_date":"2010-03-17","description":"DELIVERY + ORDER FOR BACKTAG CEMENT.","key":"CONT_AWD_AG6395D100331_12K3_AG6395C060019_12K3","obligated":602601.6,"piid":"AG6395D100331","recipient":{"uei":"C1D7UA31LAK8","display_name":"M + J RUSCOE"},"total_contract_value":602601.6},{"award_date":"2009-12-16","description":"DELIVERY + ORDER FOR BACKTAG CEMENT.","key":"CONT_AWD_AG6395D100186_12K3_AG6395C060019_12K3","obligated":101683.2,"piid":"AG6395D100186","recipient":{"uei":"C1D7UA31LAK8","display_name":"M + J RUSCOE"},"total_contract_value":101683.2},{"award_date":"2009-08-04","description":"DELIVERY + ORDER FOR CLN#10, #11, #12 BACKTAG CEMENT IN TUBE, PINT, QUART SIZE CONTAINERS","key":"CONT_AWD_AG6395D090553_12K3_AG6395C060019_12K3","obligated":431080.0,"piid":"AG6395D090553","recipient":{"uei":"C1D7UA31LAK8","display_name":"M + J RUSCOE"},"total_contract_value":431080.0},{"award_date":"2009-04-23","description":"RUSCOE + MFG CTR#AG6395C060019, DELIVERY ORDER FOR CLN#10, #11, #12 BACKTAG CEMENT + IN TUBE, PINT, QUART SIZE CONTAINERS","key":"CONT_AWD_AG6395D090363_12K3_AG6395C060019_12K3","obligated":431080.0,"piid":"AG6395D090363","recipient":{"uei":"C1D7UA31LAK8","display_name":"M + J RUSCOE"},"total_contract_value":431080.0},{"award_date":"2008-10-23","description":"RUSCOE + MFG CTR#AG6395C060019, DELIVERY ORDER FOR CLN#10, #11, #12 BACKTAG CEMENT + IN TUBE, PINT, QUART SIZE CONTAINERS","key":"CONT_AWD_AG6395D090056_12K3_AG6395C060019_12K3","obligated":431082.24,"piid":"AG6395D090056","recipient":{"uei":"C1D7UA31LAK8","display_name":"M + J RUSCOE"},"total_contract_value":431082.24},{"award_date":"2008-05-13","description":"BACKTAG + CEMENT.","key":"CONT_AWD_AG6395D080395_12K3_AG6395C060018_12K3","obligated":61409.28,"piid":"AG6395D080395","recipient":{"uei":"GDVZPLNAKLL9","display_name":"STEVEN + INDUSTRIES INC"},"total_contract_value":61409.28},{"award_date":"2008-03-05","description":"RUSCOE + MFG CTR#AG6395C060019, DELIVERY ORDER FOR CLN#7 CEMENT IN TUBE SIZE CONTAINERS + PACKED 24/BX TOTAL 11,000 BXS INCLUDED 10 % OVERAGE. (1,320,000 OZ) WHSE STOCK + #Z888-00-000-0253","key":"CONT_AWD_AG6395D080271_12K3_AG6395C060019_12K3","obligated":329820.0,"piid":"AG6395D080271","recipient":{"uei":"C1D7UA31LAK8","display_name":"M + J RUSCOE"},"total_contract_value":329820.0},{"award_date":"2008-01-25","description":"BACKTAG + CEMENT.","key":"CONT_AWD_AG6395D080197_12K3_AG6395C060018_12K3","obligated":288748.8,"piid":"AG6395D080197","recipient":{"uei":"GDVZPLNAKLL9","display_name":"STEVEN + INDUSTRIES INC"},"total_contract_value":288748.8}],"meta":{"vehicle_uuid":"8597845d-2a4c-5acd-99ff-1f5fe348501e","solicitation_identifier":"002-M-APHIS-06"}}' + headers: + CF-RAY: + - 9f73b8255b6f9934-JAX + Connection: + - keep-alive + Content-Type: + - application/json + Date: + - Wed, 06 May 2026 00:03:00 GMT + Nel: + - '{"report_to":"cf-nel","success_fraction":0.0,"max_age":604800}' + Report-To: + - '{"group":"cf-nel","max_age":604800,"endpoints":[{"url":"https://a.nel.cloudflare.com/report/v4?s=HMF3PWMAqeXFo%2BwKj7LE49%2Fa5L0Xv8ItISm5Zfcn%2FKLUJZQlFzfYk1kIrU6EAyojStWx7tR02%2FGF83GVXSZ1kIRzeXR1FXLTIDkdPx%2BpA2kqnbtNw4Tmtg1A%2BHZvsu7ueiPRypEZlTSvYpKhzgz0"}]}' + Server: + - cloudflare + Transfer-Encoding: + - chunked + allow: + - GET, HEAD, OPTIONS + cf-cache-status: + - DYNAMIC + content-length: + - '3442' + cross-origin-opener-policy: + - same-origin + referrer-policy: + - same-origin + vary: + - Accept, Cookie + x-content-type-options: + - nosniff + x-execution-time: + - 0.090s + x-frame-options: + - DENY + x-ratelimit-burst-limit: + - '1000' + x-ratelimit-burst-remaining: + - '978' + x-ratelimit-burst-reset: + - '28' + x-ratelimit-daily-limit: + - '2000000' + x-ratelimit-daily-remaining: + - '1999978' + x-ratelimit-daily-reset: + - '86219' + x-ratelimit-limit: + - '1000' + x-ratelimit-remaining: + - '978' + x-ratelimit-reset: + - '28' + x-results-counttype: + - exact + status: + code: 200 + message: OK +version: 1 diff --git a/tests/cassettes/TestVehiclesIntegration.test_list_vehicles_uses_default_shape_and_search b/tests/cassettes/TestVehiclesIntegration.test_list_vehicles_uses_default_shape_and_search index 12ed35c..5133056 100644 --- a/tests/cassettes/TestVehiclesIntegration.test_list_vehicles_uses_default_shape_and_search +++ b/tests/cassettes/TestVehiclesIntegration.test_list_vehicles_uses_default_shape_and_search @@ -13,33 +13,95 @@ interactions: user-agent: - python-httpx/0.28.1 method: GET - uri: https://tango.makegov.com/api/vehicles/?page=1&limit=10&shape=uuid%2Csolicitation_identifier%2Corganization_id%2Cawardee_count%2Corder_count%2Cvehicle_obligations%2Cvehicle_contracts_value%2Csolicitation_title%2Csolicitation_date&search=GSA + uri: https://tango.makegov.com/api/vehicles/?page=1&limit=10&shape=uuid%2Csolicitation_identifier%2Cis_synthetic_solicitation%2Cprogram_acronym%2Corganization_id%2Corganization%2Cvehicle_type%2Cdescription%2Cidv_count%2Cawardee_count%2Corder_count%2Ctotal_obligated%2Cvehicle_obligations%2Cvehicle_contracts_value%2Clatest_award_date%2Csolicitation_title%2Csolicitation_date&search=GSA response: body: - string: '{"count":73,"next":"https://tango.makegov.com/api/vehicles/?limit=10&page=2&search=GSA&shape=uuid%2Csolicitation_identifier%2Corganization_id%2Cawardee_count%2Corder_count%2Cvehicle_obligations%2Cvehicle_contracts_value%2Csolicitation_title%2Csolicitation_date","previous":null,"results":[{"uuid":"6741a166-ae8e-57ba-b156-76f9137febad","solicitation_identifier":"15F06725Q0000150","organization_id":"08ba1358-7dad-5228-9540-0a3718bc621d","awardee_count":4,"order_count":5,"vehicle_obligations":3104682.7,"vehicle_contracts_value":29044858.96,"solicitation_title":"Curriculum - Development Synopsis/Solicitation - Updated GSA site","solicitation_date":"2025-04-10"},{"uuid":"1e03d563-afa5-565f-be04-ba64b0ec053c","solicitation_identifier":"2FYB-BJ-030001-B","organization_id":null,"awardee_count":46,"order_count":7458,"vehicle_obligations":139267557.14,"vehicle_contracts_value":607178722.14,"solicitation_title":"Cameras, - Photographic Printers and Related Supplies and Services","solicitation_date":"2001-03-01"},{"uuid":"6fea329b-c906-5857-8384-6451a4c93430","solicitation_identifier":"36C10X23Q0022","organization_id":"f6c88e61-3d34-5685-a5ab-670858289883","awardee_count":4,"order_count":14,"vehicle_obligations":2356056.0,"vehicle_contracts_value":2356056.0,"solicitation_title":"U008--Acquisition + string: '{"count":103,"next":"https://tango.makegov.com/api/vehicles/?limit=10&page=2&search=GSA&shape=uuid%2Csolicitation_identifier%2Cis_synthetic_solicitation%2Cprogram_acronym%2Corganization_id%2Corganization%2Cvehicle_type%2Cdescription%2Cidv_count%2Cawardee_count%2Corder_count%2Ctotal_obligated%2Cvehicle_obligations%2Cvehicle_contracts_value%2Clatest_award_date%2Csolicitation_title%2Csolicitation_date","previous":null,"results":[{"awardee_count":0,"description":["EMDX + RESEARCH AND CONSULTING ASSOCIATES, LLC, NATIONAL ANIMAL CARE BLANKET PURCHASE + AGREEMENT FOR THE AGRICULTURAL RESEARCH SERVICE","SCITECH SERVICES INC NATIONAL + ANIMAL CARE BLANKET PURCHASE AGREEMENT FOR THE AGRICULTURAL RESEARCH SERVICE","THE + MCCONNELL GROUP, INC., NATIONAL ANIMAL CARE BLANKET PURCHASE AGREEMENT FOR + THE AGRICULTURAL RESEARCH SERVICE","ABANTEARE LLC NATIONAL ANIMAL CARE BLANKET + PURCHASE AGREEMENT FOR THE AGRICULTURAL RESEARCH SERVICE","BCA FEDERAL, LLC, + NATIONAL ANIMAL CARE BLANKET PURCHASE AGREEMENT FOR THE AGRICULTURAL RESEARCH + SERVICE","FEFA, LLC NATIONAL ANIMAL CARE BLANKET PURCHASE AGREEMENT FOR THE + AGRICULTURAL RESEARCH SERVICE","SOBRAN, INC., NATIONAL ANIMAL CARE BLANKET + PURCHASE AGREEMENT FOR THE AGRICULTURAL RESEARCH SERVICE"],"idv_count":7,"is_synthetic_solicitation":false,"latest_award_date":"2026-05-01","order_count":0,"organization":{"organization_id":"0ef2831b-d699-50c7-8326-63541a75fa11","office_code":"1232SA","office_name":"USDA + ARS AFM APD","agency_code":"12H2","agency_name":"AGRICULTURAL RESEARCH SERVICE","department_code":"012","department_name":"AGRICULTURE, + DEPARTMENT OF"},"organization_id":"0ef2831b-d699-50c7-8326-63541a75fa11","program_acronym":"GSA","solicitation_date":null,"solicitation_identifier":"1232SA26Q0019","solicitation_title":null,"total_obligated":0.0,"uuid":"3fdd49c6-bf14-57b5-9b56-05a5d3adb35a","vehicle_contracts_value":0.0,"vehicle_obligations":0.0,"vehicle_type":{"code":"E","description":"BPA"}},{"awardee_count":2,"description":["VARIOUS + DIVISIONS WITHIN THE AGENCY REQUIRE CURRICULUM DEVELOPMENT SUPPORT. EACH DEPARTMENT/DIVISION + WILL BE REQUIRED TO USE THE ANALYZE, DESIGN, DEVELOP, IMPLEMENT AND EVALUATION + (ADDIE) MODULE.","DIVISIONS WITHIN THE AGENCY REQUIRE CURRICULUM DEVELOPMENT + SUPPORT. EACH DEPT/DIV WILL REQUIRE THE USE OF ANALYZE, DESIGN, DEVELOP, IMPLEMENT + AND EVALUATION (ADDIE) MODULE. EACH DEPT/DIV SPECIFIC REQUIREMENTS WILL BE + PROVIDED ON SEPARATE BPA CALL."],"idv_count":4,"is_synthetic_solicitation":false,"latest_award_date":"2025-09-04","order_count":5,"organization":{"organization_id":"ea1c7a92-5838-5641-99ba-1527f4524306","office_code":"15F067","office_name":"FBI-JEH","agency_code":"1549","agency_name":"FEDERAL + BUREAU OF INVESTIGATION","department_code":"015","department_name":"JUSTICE, + DEPARTMENT OF"},"organization_id":"ea1c7a92-5838-5641-99ba-1527f4524306","program_acronym":null,"solicitation_date":null,"solicitation_identifier":"15F06725Q0000150","solicitation_title":"Curriculum + Development Synopsis/Solicitation - Updated GSA site","total_obligated":3104682.7,"uuid":"6741a166-ae8e-57ba-b156-76f9137febad","vehicle_contracts_value":29044858.959999997,"vehicle_obligations":3104682.7,"vehicle_type":{"code":"E","description":"BPA"}},{"awardee_count":1,"description":["MULTI-AWARD + BLANKET PURCHASE AGREEMENT (BPA) FOR EDUCATION, OUTREACH & ANALYSIS"],"idv_count":4,"is_synthetic_solicitation":false,"latest_award_date":"2025-02-13","order_count":1,"organization":{"organization_id":"5b904ace-8e3f-5909-a991-3e7297c5ffeb","office_code":"1605C2","office_name":"DOL + - CAS DIVISION 2 PROCUREMENT","agency_code":"1605","agency_name":"OFFICE OF + THE ASSISTANT SECRETARY FOR ADMINISTRATION AND MANAGEMENT","department_code":"016","department_name":"LABOR, + DEPARTMENT OF"},"organization_id":"5b904ace-8e3f-5909-a991-3e7297c5ffeb","program_acronym":"GSA","solicitation_date":null,"solicitation_identifier":"1605C2-24-Q-00026","solicitation_title":null,"total_obligated":50000.0,"uuid":"9f042142-a09b-5244-9b3d-a638a4e2f219","vehicle_contracts_value":739950.6,"vehicle_obligations":50000.0,"vehicle_type":{"code":"E","description":"BPA"}},{"awardee_count":3,"description":["VAAA + ACQUISITION WORKFORCE TRAINING, MULTIPLE AWARD BLANKET PURCHASE AGREEMENT + (BPA)"],"idv_count":4,"is_synthetic_solicitation":false,"latest_award_date":"2024-02-12","order_count":14,"organization":{"organization_id":"d8247025-812e-5403-954c-09577eab4a91","office_code":"36C10X","office_name":"SAC + FREDERICK (36C10X)","agency_code":"3600","agency_name":"VETERANS AFFAIRS, + DEPARTMENT OF","department_code":"036","department_name":"VETERANS AFFAIRS, + DEPARTMENT OF"},"organization_id":"d8247025-812e-5403-954c-09577eab4a91","program_acronym":null,"solicitation_date":null,"solicitation_identifier":"36C10X23Q0022","solicitation_title":"U008--Acquisition Workforce Training Multiple Award BPA - NEW GSA Contract Number Business Management - Research Associates, Inc","solicitation_date":"2024-04-05"},{"uuid":"e99312ce-b377-5fac-81ce-7f3f19b548c4","solicitation_identifier":"36C26120Q0012","organization_id":"f6c88e61-3d34-5685-a5ab-670858289883","awardee_count":3,"order_count":92,"vehicle_obligations":51415076.89,"vehicle_contracts_value":51498956.84,"solicitation_title":"Q301--Quest - FSS-BPA GSA Schedule 621 II","solicitation_date":"2020-09-28"},{"uuid":"26090608-dfc4-5245-8f1d-043f40d3cedd","solicitation_identifier":"3FNG-RG-020001-B","organization_id":"edfcdf18-d5d1-5e9a-8563-d8ed1f2844ce","awardee_count":111,"order_count":3766,"vehicle_obligations":231871038.22,"vehicle_contracts_value":235312118.92,"solicitation_title":"Professional - Audio/Video, Telemetry/Tracking, Recording, Reproducing and Signal Data Solutions","solicitation_date":"2010-11-30"},{"uuid":"1293190d-b755-5f61-a484-211f4425aad2","solicitation_identifier":"3FNJ-C1-000001-B","organization_id":null,"awardee_count":275,"order_count":51247,"vehicle_obligations":3570831390.91,"vehicle_contracts_value":11123777947.32,"solicitation_title":"Office, - Imaging and Document","solicitation_date":"1999-04-01"},{"uuid":"74ef741b-f0bc-506a-94cc-8252264a0237","solicitation_identifier":"3FNJ-C1-000001-B","organization_id":"edfcdf18-d5d1-5e9a-8563-d8ed1f2844ce","awardee_count":294,"order_count":10410,"vehicle_obligations":3351983917.63,"vehicle_contracts_value":7967591185.76,"solicitation_title":"Office, - Imaging and Document","solicitation_date":"2010-10-01"},{"uuid":"1f8a3ce7-a58c-5d36-907d-bf67f96e2cd4","solicitation_identifier":"3QSA-JB-100001-B","organization_id":"edfcdf18-d5d1-5e9a-8563-d8ed1f2844ce","awardee_count":497,"order_count":24312,"vehicle_obligations":2067723956.69,"vehicle_contracts_value":2149663259.46,"solicitation_title":"Furniture","solicitation_date":"2010-10-04"},{"uuid":"4b755976-75c2-573a-bd3a-92147261df2f","solicitation_identifier":"47PB0023R0012","organization_id":"17417361-31f1-5514-9623-5ef7b0ae02db","awardee_count":24,"order_count":47,"vehicle_obligations":22210407.27,"vehicle_contracts_value":22210407.27,"solicitation_title":"GSA - Region 1 Construction IDIQ - North/Boston/South Zones","solicitation_date":"2023-06-30"},{"uuid":"59cee065-88e2-5a98-be93-004df7e23d18","solicitation_identifier":"47PB0023R0059","organization_id":"17417361-31f1-5514-9623-5ef7b0ae02db","awardee_count":7,"order_count":18,"vehicle_obligations":1171435.78,"vehicle_contracts_value":1171435.78,"solicitation_title":"Architectural - and Engineering Multiple Award IDIQ New England","solicitation_date":"2023-09-13"}]}' + Research Associates, Inc","total_obligated":2359268.78,"uuid":"6fea329b-c906-5857-8384-6451a4c93430","vehicle_contracts_value":2359268.78,"vehicle_obligations":2359268.78,"vehicle_type":{"code":"E","description":"BPA"}},{"awardee_count":3,"description":["REFERENCE + LABORATORY SERVICES FOR VISN 21","REFERENCE LABORATORY SERVICES FOR VISN 21."],"idv_count":3,"is_synthetic_solicitation":false,"latest_award_date":"2020-09-25","order_count":93,"organization":{"organization_id":"2c45fd53-3590-5589-a188-1f34917b3de5","office_code":"36C261","office_name":"261-NETWORK + CONTRACT OFFICE 21 (36C261)","agency_code":"3600","agency_name":"VETERANS + AFFAIRS, DEPARTMENT OF","department_code":"036","department_name":"VETERANS + AFFAIRS, DEPARTMENT OF"},"organization_id":"2c45fd53-3590-5589-a188-1f34917b3de5","program_acronym":null,"solicitation_date":null,"solicitation_identifier":"36C26120Q0012","solicitation_title":"Q301--Quest + FSS-BPA GSA Schedule 621 II","total_obligated":52052266.47,"uuid":"e99312ce-b377-5fac-81ce-7f3f19b548c4","vehicle_contracts_value":53523209.29,"vehicle_obligations":52052266.47,"vehicle_type":{"code":"E","description":"BPA"}},{"awardee_count":6,"description":["GSA + LEASING SUPPORT SERVICES"],"idv_count":9,"is_synthetic_solicitation":false,"latest_award_date":"2020-05-13","order_count":911,"organization":{"organization_id":"6f5ca983-9b09-55b6-9ad0-f0ce1c2d06c1","office_code":"47PA05","office_name":"PBS + R00 CENTER FOR BROKER SERVICES","agency_code":"4740","agency_name":"PUBLIC + BUILDINGS SERVICE","department_code":"047","department_name":"GENERAL SERVICES + ADMINISTRATION"},"organization_id":"6f5ca983-9b09-55b6-9ad0-f0ce1c2d06c1","program_acronym":null,"solicitation_date":null,"solicitation_identifier":"47PA0519R0001","solicitation_title":"GSA + Leasing Support Services Plus","total_obligated":0.0,"uuid":"5dbdd43a-d8aa-56eb-a11f-78e0ded35642","vehicle_contracts_value":907.02,"vehicle_obligations":0.0,"vehicle_type":{"code":"B","description":"IDC"}},{"awardee_count":7,"description":["ELECTRIC + SUPPLY","PROVIDE ELECTRIC UTILITY SUPPLY IN ERCOT FOR GROUPS 9 FOR AGENCIES + LISTED IN EXHIBIT 1 OF THIS CONTRACT.","PROVIDE ELECTRIC UTILITY SUPPLY IN + PJM FOR GROUPS 3 5 7 FOR AGENCIES LISTED IN EXHIBIT 1 OF THIS CONTRACT.","PROVIDE + ELECTRIC UTILITY SUPPLY IN PJM FOR GROUPS 2 4 6 8 FOR AGENCIES LISTED IN EXHIBIT + 1 OF THIS CONTRACT."],"idv_count":9,"is_synthetic_solicitation":false,"latest_award_date":"2025-08-18","order_count":88,"organization":{"organization_id":"949facff-0fda-5062-972b-01ad17491bc6","office_code":"47PA08","office_name":"PBS + RETAIL UTILITY PROCUREMENTS","agency_code":"4740","agency_name":"PUBLIC BUILDINGS + SERVICE","department_code":"047","department_name":"GENERAL SERVICES ADMINISTRATION"},"organization_id":"949facff-0fda-5062-972b-01ad17491bc6","program_acronym":null,"solicitation_date":null,"solicitation_identifier":"47PA0825R0002","solicitation_title":"FY25 + GSA Streamlined Retail Electric Solicitation","total_obligated":44802108.86,"uuid":"6aa9d616-4d60-52b3-bfb7-a4f4ba332a3c","vehicle_contracts_value":105773964.96,"vehicle_obligations":44802108.86,"vehicle_type":{"code":"B","description":"IDC"}},{"awardee_count":0,"description":["ELECTRIC + SUPPLY FOR FIXED PRICES, PRICING GROUP 3 IN THE PJM BALANCING AUTHORITY","ELECTRIC + SUPPLY FOR FIXED PRICES, PRICING GROUPS 5 AND 6 IN THE PJM BALANCING AUTHORITY","ELECTRIC + SUPPLY FOR FIXED PRICES, PRICING GROUPS 1 AND 2 IN THE PJM BALANCING AUTHORITY","ELECTRIC + SUPPLY FOR FIXED PRICES, PRICING GROUP 4 IN THE PJM BALANCING AUTHORITY"],"idv_count":4,"is_synthetic_solicitation":false,"latest_award_date":"2026-03-23","order_count":0,"organization":{"organization_id":"949facff-0fda-5062-972b-01ad17491bc6","office_code":"47PA08","office_name":"PBS + RETAIL UTILITY PROCUREMENTS","agency_code":"4740","agency_name":"PUBLIC BUILDINGS + SERVICE","department_code":"047","department_name":"GENERAL SERVICES ADMINISTRATION"},"organization_id":"949facff-0fda-5062-972b-01ad17491bc6","program_acronym":null,"solicitation_date":null,"solicitation_identifier":"47PA0826R0001","solicitation_title":"FY26 + GSA Streamlined Retail Electric Solicitation","total_obligated":0.0,"uuid":"7c370689-aad8-5ba3-9498-c018bc5df121","vehicle_contracts_value":0.0,"vehicle_obligations":0.0,"vehicle_type":{"code":"B","description":"IDC"}},{"awardee_count":8,"description":["MULTIPLE + AWARD, INDEFINITE DELIVERY/INDEFINITE QUANTITY (IDIQ) WITH MINIMUM GUARANTEE + FOR CONSTRUCTION SERVICES FOR GSA-OWNED AND LEASED FEDERAL FACILITIES IN REGION + 1 SOUTH ZONE.","MULTIPLE AWARD, INDEFINITE DELIVERY/INDEFINITE QUANTITY (IDIQ) + WITH MINIMUM GUARANTEE FOR CONSTRUCTION SERVICES FOR GSA-OWNED AND LEASED + FEDERAL FACILITIES IN REGION 1 NORTH ZONE.","MULTIPLE AWARD, INDEFINITE DELIVERY/INDEFINITE + QUANTITY (IDIQ) WITH MINIMUM GUARANTEE FOR CONSTRUCTION SERVICES FOR GSA-OWNED + AND LEASED FEDERAL FACILITIES IN REGION 1 BOSTON ZONE."],"idv_count":24,"is_synthetic_solicitation":false,"latest_award_date":"2025-02-24","order_count":51,"organization":{"organization_id":"2fd4b124-5131-5ab9-9091-e2ee27274d0e","office_code":"47PB00","office_name":"PBS + R1 ACQ MANAGEMENT DIVISION","agency_code":"4740","agency_name":"PUBLIC BUILDINGS + SERVICE","department_code":"047","department_name":"GENERAL SERVICES ADMINISTRATION"},"organization_id":"2fd4b124-5131-5ab9-9091-e2ee27274d0e","program_acronym":null,"solicitation_date":null,"solicitation_identifier":"47PB0023R0012","solicitation_title":"GSA + Region 1 Construction IDIQ - North/Boston/South Zones","total_obligated":24000257.07,"uuid":"4b755976-75c2-573a-bd3a-92147261df2f","vehicle_contracts_value":24000257.07,"vehicle_obligations":24000257.07,"vehicle_type":{"code":"B","description":"IDC"}},{"awardee_count":0,"description":["CARIBBEAN + CONSTRUCTION & DESIGN-BUILD CONSTRUCTION SERVICES"],"idv_count":7,"is_synthetic_solicitation":false,"latest_award_date":"2025-12-16","order_count":0,"organization":{"organization_id":"54839cf5-aa68-5304-8f46-9aa63eaf2ba5","office_code":"47PC55","office_name":"PBS + PROJECT DELIVERY EAST - BRANCH E","agency_code":"4740","agency_name":"PUBLIC + BUILDINGS SERVICE","department_code":"047","department_name":"GENERAL SERVICES + ADMINISTRATION"},"organization_id":"54839cf5-aa68-5304-8f46-9aa63eaf2ba5","program_acronym":null,"solicitation_date":null,"solicitation_identifier":"47PC0222R0019","solicitation_title":"GSA + R2 - Caribbean Construction IDIQ","total_obligated":0.0,"uuid":"1a1704af-7d47-5293-b189-cf6a8c7ca35f","vehicle_contracts_value":0.0,"vehicle_obligations":0.0,"vehicle_type":{"code":"B","description":"IDC"}}]}' headers: CF-RAY: - - 9d71a7968b13fc89-MSP + - 9f73b81bf9a460c7-JAX Connection: - keep-alive Content-Type: - application/json Date: - - Wed, 04 Mar 2026 14:43:42 GMT + - Wed, 06 May 2026 00:02:58 GMT Nel: - '{"report_to":"cf-nel","success_fraction":0.0,"max_age":604800}' Report-To: - - '{"group":"cf-nel","max_age":604800,"endpoints":[{"url":"https://a.nel.cloudflare.com/report/v4?s=5%2BHepsdgjcU87kM%2BNnTgEm5bAgw8L9ZsZHeSXeBCTE%2BWnksTynGg8TJ56KoKVetk3pFnm%2Bz6OTI%2BHQ3lraKGlHtfXJrFZtMZU6d5rEzcXciLRMAIthAZ1V8Z"}]}' + - '{"group":"cf-nel","max_age":604800,"endpoints":[{"url":"https://a.nel.cloudflare.com/report/v4?s=mvbE0znCQMx6U%2BgaEc5wluqJwJD6ucje1qKsBbZ%2F7ZWCVecTQHyBrlSBGPO44JvVemBkaSui4vuB6wXTaW1QuBpGD9oaRlxmkXUigS2jf%2BDoqMKs2mlfmejcjhk6xq8MNUy%2B0sfLHo8NYz%2FHVLiQ"}]}' Server: - cloudflare Transfer-Encoding: @@ -49,7 +111,7 @@ interactions: cf-cache-status: - DYNAMIC content-length: - - '3950' + - '11445' cross-origin-opener-policy: - same-origin referrer-policy: @@ -59,27 +121,29 @@ interactions: x-content-type-options: - nosniff x-execution-time: - - 0.578s + - 0.304s x-frame-options: - DENY x-ratelimit-burst-limit: - '1000' x-ratelimit-burst-remaining: - - '911' + - '987' x-ratelimit-burst-reset: - - '7' + - '29' x-ratelimit-daily-limit: - '2000000' x-ratelimit-daily-remaining: - - '1999671' + - '1999987' x-ratelimit-daily-reset: - - '84967' + - '86221' x-ratelimit-limit: - '1000' x-ratelimit-remaining: - - '911' + - '987' x-ratelimit-reset: - - '7' + - '29' + x-results-counttype: + - exact status: code: 200 message: OK diff --git a/tests/cassettes/TestVehiclesIntegration.test_list_vehicles_with_ordering b/tests/cassettes/TestVehiclesIntegration.test_list_vehicles_with_ordering new file mode 100644 index 0000000..991fabb --- /dev/null +++ b/tests/cassettes/TestVehiclesIntegration.test_list_vehicles_with_ordering @@ -0,0 +1,78 @@ +interactions: +- request: + body: '' + headers: + accept: + - '*/*' + accept-encoding: + - gzip, deflate + connection: + - keep-alive + host: + - tango.makegov.com + user-agent: + - python-httpx/0.28.1 + method: GET + uri: https://tango.makegov.com/api/vehicles/?page=1&limit=10&shape=uuid%2Cvehicle_obligations&ordering=-vehicle_obligations + response: + body: + string: '{"count":20452,"next":"https://tango.makegov.com/api/vehicles/?limit=10&ordering=-vehicle_obligations&page=2&shape=uuid%2Cvehicle_obligations","previous":null,"results":[{"uuid":"d44a2444-0e5f-5dda-ac83-a950dd91beb3","vehicle_obligations":88745333134.01},{"uuid":"054a30f0-c047-5627-8a3f-814f4bdfa497","vehicle_obligations":76190247502.85},{"uuid":"3adb66a1-c2cb-5b0f-a16f-03fed8e47dff","vehicle_obligations":75840315414.76},{"uuid":"55395c6f-2158-5426-ba95-48fc151e513e","vehicle_obligations":55000151945.38},{"uuid":"045b7843-cbf0-5b53-896b-090001fd3e07","vehicle_obligations":53557899297.46},{"uuid":"87b8f04a-a351-592b-acff-158228b8ef8e","vehicle_obligations":46132104694.13},{"uuid":"d0d2a5f7-8396-5b6b-8ccf-708a4e004271","vehicle_obligations":40688569449.2},{"uuid":"84a7bd7f-dca6-5fa8-ac45-a66fc8abfe39","vehicle_obligations":36297028262.71},{"uuid":"afe27373-bd31-52e7-b513-a8a46f770bdf","vehicle_obligations":31965503650.4},{"uuid":"16506c41-d5ac-5359-8223-3680e1dfbb1b","vehicle_obligations":30876687828.34}]}' + headers: + CF-RAY: + - 9f73b821b997ae89-JAX + Connection: + - keep-alive + Content-Type: + - application/json + Date: + - Wed, 06 May 2026 00:02:59 GMT + Nel: + - '{"report_to":"cf-nel","success_fraction":0.0,"max_age":604800}' + Report-To: + - '{"group":"cf-nel","max_age":604800,"endpoints":[{"url":"https://a.nel.cloudflare.com/report/v4?s=JRSGPXnHWe%2BJHnFlM9CPNQmPYaiea8E9NxE7EGsxyeoBsplHQSA71Sh86DZN6aMt4S188K3XPesAlQO9qTVHHhTa79Me0D8NYFtbgE6brma68SxKyKV0EEYdE2SdNXt1Bra9G6woqYqD8Sbfd4aP"}]}' + Server: + - cloudflare + Transfer-Encoding: + - chunked + allow: + - GET, HEAD, OPTIONS + cf-cache-status: + - DYNAMIC + content-length: + - '1019' + cross-origin-opener-policy: + - same-origin + referrer-policy: + - same-origin + vary: + - Accept, Cookie + x-content-type-options: + - nosniff + x-execution-time: + - 0.141s + x-frame-options: + - DENY + x-ratelimit-burst-limit: + - '1000' + x-ratelimit-burst-remaining: + - '982' + x-ratelimit-burst-reset: + - '28' + x-ratelimit-daily-limit: + - '2000000' + x-ratelimit-daily-remaining: + - '1999982' + x-ratelimit-daily-reset: + - '86220' + x-ratelimit-limit: + - '1000' + x-ratelimit-remaining: + - '982' + x-ratelimit-reset: + - '28' + x-results-counttype: + - exact + status: + code: 200 + message: OK +version: 1 diff --git a/tests/integration/test_itdashboard_integration.py b/tests/integration/test_itdashboard_integration.py index b160564..cd4929a 100644 --- a/tests/integration/test_itdashboard_integration.py +++ b/tests/integration/test_itdashboard_integration.py @@ -62,9 +62,7 @@ def validate_investment_fields(investment, comprehensive: bool = False) -> None: ): value = _get(investment, field) if value is not None: - assert isinstance(value, str), ( - f"Investment '{field}' must be string, got {type(value)}" - ) + assert isinstance(value, str), f"Investment '{field}' must be string, got {type(value)}" updated_time = _get(investment, "updated_time") if updated_time is not None: @@ -94,9 +92,7 @@ class TestITDashboardIntegration: ("custom", "uii,agency_name,investment_title,updated_time"), ], ) - def test_list_itdashboard_investments_with_shapes( - self, tango_client, shape_name, shape_value - ): + def test_list_itdashboard_investments_with_shapes(self, tango_client, shape_name, shape_value): """Test listing investments with different shapes.""" kwargs: dict = {"limit": 5} if shape_value is not None: @@ -211,9 +207,7 @@ def test_filter_by_updated_time_range(self, tango_client): validate_no_parsing_errors(investment) updated = _get(investment, "updated_time") if updated is not None: - assert updated.year == 2026, ( - f"Expected updated_time in 2026, got {updated!r}" - ) + assert updated.year == 2026, f"Expected updated_time in 2026, got {updated!r}" # ------------------------------------------------------------------ # Business+ filters @@ -222,9 +216,7 @@ def test_filter_by_updated_time_range(self, tango_client): @handle_api_exceptions("itdashboard") def test_filter_by_agency_name_text(self, tango_client): """Business+: text search across agency name (icontains).""" - response = tango_client.list_itdashboard_investments( - limit=5, agency_name="defense" - ) + response = tango_client.list_itdashboard_investments(limit=5, agency_name="defense") validate_pagination(response) if response.results: @@ -260,9 +252,7 @@ def test_filter_by_cio_rating_max(self, tango_client): @handle_api_exceptions("itdashboard") def test_filter_by_performance_risk(self, tango_client): """Business+: investments with at least one NOT MET performance metric.""" - response = tango_client.list_itdashboard_investments( - limit=5, performance_risk=True - ) + response = tango_client.list_itdashboard_investments(limit=5, performance_risk=True) validate_pagination(response) for investment in response.results: validate_investment_fields(investment) @@ -288,9 +278,7 @@ def test_funding_and_cio_evaluation_expansions(self, tango_client): assert _get(investment, "uii") is not None funding = _get(investment, "funding") if funding is not None: - assert isinstance(funding, dict), ( - f"funding must be dict, got {type(funding)}" - ) + assert isinstance(funding, dict), f"funding must be dict, got {type(funding)}" cio_eval = _get(investment, "cio_evaluation") if cio_eval is not None: assert isinstance(cio_eval, list), ( diff --git a/tests/integration/test_vehicles_idvs_integration.py b/tests/integration/test_vehicles_idvs_integration.py index 6f7cada..5802bd5 100644 --- a/tests/integration/test_vehicles_idvs_integration.py +++ b/tests/integration/test_vehicles_idvs_integration.py @@ -21,6 +21,7 @@ TANGO_REFRESH_CASSETTES=true TANGO_API_KEY=xxx pytest tests/integration/test_vehicles_idvs_integration.py """ +import warnings from datetime import date from decimal import Decimal @@ -30,6 +31,13 @@ from tests.integration.validation import validate_no_parsing_errors, validate_pagination +def _field(obj, name): + """Read a field by name from a dict-or-attr-style shaped instance.""" + if isinstance(obj, dict): + return obj.get(name) + return getattr(obj, name, None) + + @pytest.mark.vcr() @pytest.mark.integration class TestVehiclesIntegration: @@ -83,6 +91,39 @@ def test_list_vehicles_uses_default_shape_and_search(self, tango_client): ) assert isinstance(solicitation_date, date), "solicitation_date should be date" + # New vehicle fields. All optional — only verify type when present. + is_synth = _field(vehicle, "is_synthetic_solicitation") + if is_synth is not None: + assert isinstance(is_synth, bool), "is_synthetic_solicitation should be bool" + + idv_count = _field(vehicle, "idv_count") + if idv_count is not None: + assert isinstance(idv_count, int), "idv_count should be int" + + total_obligated = _field(vehicle, "total_obligated") + if total_obligated is not None: + assert isinstance(total_obligated, Decimal), "total_obligated should be Decimal" + + latest_award_date = _field(vehicle, "latest_award_date") + if latest_award_date is not None: + assert isinstance(latest_award_date, date), "latest_award_date should be date" + + organization = _field(vehicle, "organization") + if organization is not None: + assert isinstance(organization, dict), "organization should be a dict" + allowed = { + "organization_id", + "office_code", + "office_name", + "agency_code", + "agency_name", + "department_code", + "department_name", + } + assert set(organization).issubset(allowed), ( + f"organization keys outside allowed set: {set(organization) - allowed}" + ) + @handle_api_exceptions("vehicles") def test_get_vehicle_supports_joiner_and_flat_lists(self, tango_client): """Test getting a single vehicle with joiner and flat_lists parameters @@ -104,10 +145,11 @@ def test_get_vehicle_supports_joiner_and_flat_lists(self, tango_client): ) assert vehicle_uuid is not None, "Vehicle UUID should be present" - # Test with flat, flat_lists, and joiner + # Test with flat, flat_lists, and joiner. Uses the post-cutover `organization` + # leaf field (the prior `opportunity(...)` expansion is now deprecated). vehicle = tango_client.get_vehicle( vehicle_uuid, - shape="uuid,opportunity(title)", + shape="uuid,organization", flat=True, flat_lists=True, joiner="__", @@ -118,15 +160,15 @@ def test_get_vehicle_supports_joiner_and_flat_lists(self, tango_client): "Vehicle uuid should be present" ) - # If opportunity is present, verify it's accessible - if vehicle.get("opportunity") is not None or ( - hasattr(vehicle, "opportunity") and vehicle.opportunity is not None - ): - opportunity = ( - vehicle.get("opportunity") if isinstance(vehicle, dict) else vehicle.opportunity - ) - if isinstance(opportunity, dict): - assert "title" in opportunity or hasattr(opportunity, "title") + # When flattened with joiner="__", organization fields surface as + # `organization__office_code` / `organization__office_name`. Assert no + # dotted keys leaked through (organization may be null on this UUID, in + # which case flattening produces no organization-prefixed keys at all). + keys = list(vehicle) if isinstance(vehicle, dict) else [] + org_keys = [k for k in keys if k.startswith("organization")] + assert all("." not in k for k in org_keys), ( + f"flattened keys should use joiner='__', not '.': {org_keys}" + ) @handle_api_exceptions("vehicles") def test_list_vehicle_awardees_uses_default_shape(self, tango_client): @@ -192,6 +234,109 @@ def test_list_vehicle_awardees_uses_default_shape(self, tango_client): if isinstance(recipient, dict): assert "display_name" in recipient or hasattr(recipient, "display_name") + @handle_api_exceptions("vehicles") + def test_list_vehicles_with_ordering(self, tango_client): + """`ordering=-vehicle_obligations` returns vehicles sorted descending.""" + response = tango_client.list_vehicles( + limit=10, + ordering="-vehicle_obligations", + shape="uuid,vehicle_obligations", + ) + validate_pagination(response) + + obligations = [ + v for v in (_field(r, "vehicle_obligations") for r in response.results) if v is not None + ] + if len(obligations) >= 2: + assert obligations == sorted(obligations, reverse=True), ( + f"Expected descending sort by vehicle_obligations, got {obligations}" + ) + + @handle_api_exceptions("vehicles") + def test_get_vehicle_with_metrics_expansion(self, tango_client): + """`metrics(*)` expansion returns the 12 metric fields with correct types.""" + list_response = tango_client.list_vehicles(limit=1) + if not list_response.results: + pytest.skip("No vehicles available to test metrics expansion") + vehicle_uuid = _field(list_response.results[0], "uuid") + assert vehicle_uuid, "Vehicle UUID should be present" + + vehicle = tango_client.get_vehicle(vehicle_uuid, shape="uuid,metrics(*)") + validate_no_parsing_errors(vehicle) + + metrics = _field(vehicle, "metrics") + if metrics is None: + pytest.skip("Vehicle has no metrics yet (upstream sync may be pending)") + + assert isinstance(metrics, dict), "metrics expansion should be a dict" + # Float-typed metrics + for fname in ( + "avg_offers_received", + "award_concentration_hhi", + "order_concentration_hhi", + "competed_rate", + "avg_order_value", + "max_order_value", + "top_recipient_share", + "recent_obligations_24mo", + "obligation_to_ceiling_ratio", + ): + value = metrics.get(fname) + if value is not None: + assert isinstance(value, float), f"{fname} should be float" + # Int-typed metrics + for fname in ("using_agency_count", "recent_orders_24mo", "days_since_last_order"): + value = metrics.get(fname) + if value is not None: + assert isinstance(value, int), f"{fname} should be int" + + @handle_api_exceptions("vehicles") + def test_list_vehicle_orders_uses_default_shape(self, tango_client): + """`/api/vehicles/{uuid}/orders/` returns task orders with the default shape applied.""" + list_response = tango_client.list_vehicles(limit=1) + if not list_response.results: + pytest.skip("No vehicles available to test list_vehicle_orders") + vehicle_uuid = _field(list_response.results[0], "uuid") + assert vehicle_uuid, "Vehicle UUID should be present" + + response = tango_client.list_vehicle_orders(vehicle_uuid, limit=10) + validate_pagination(response) + + if response.results: + order = response.results[0] + validate_no_parsing_errors(order) + assert _field(order, "key") is not None, "Order key should be present" + + award_date = _field(order, "award_date") + if award_date is not None: + assert isinstance(award_date, date), "award_date should be date" + + obligated = _field(order, "obligated") + if obligated is not None: + assert isinstance(obligated, Decimal), "obligated should be Decimal" + + def test_deprecated_shape_field_warns(self, tango_client): + """Explicitly requesting a deprecated shape field emits a DeprecationWarning.""" + # Pure unit-style assertion on the helper — no HTTP call needed, so no + # cassette / @vcr / @handle_api_exceptions decoration. Cheap to run. + with warnings.catch_warnings(record=True) as caught: + warnings.simplefilter("always") + tango_client._warn_deprecated_vehicle_shape( + "uuid,solicitation_identifier,agency_details(*)" + ) + deprecations = [w for w in caught if issubclass(w.category, DeprecationWarning)] + assert deprecations, "Expected a DeprecationWarning for `agency_details`" + message = str(deprecations[0].message) + assert "agency_details" in message + + # Sanity check: the default shape (no deprecated tokens) does NOT warn. + with warnings.catch_warnings(record=True) as caught: + warnings.simplefilter("always") + tango_client._warn_deprecated_vehicle_shape("uuid,solicitation_identifier,metrics(*)") + assert not [w for w in caught if issubclass(w.category, DeprecationWarning)], ( + "Non-deprecated shape should not emit DeprecationWarning" + ) + @pytest.mark.vcr() @pytest.mark.integration diff --git a/tests/production/test_production_smoke.py b/tests/production/test_production_smoke.py index 15be48d..83ccce4 100644 --- a/tests/production/test_production_smoke.py +++ b/tests/production/test_production_smoke.py @@ -313,6 +313,25 @@ def test_get_vehicle(self, production_client): "Vehicle uuid should be present" ) + @handle_rate_limit + @handle_auth_error + def test_list_vehicle_orders_basic(self, production_client): + """Smoke test for `/api/vehicles/{uuid}/orders/` (post-cutover endpoint).""" + list_response = production_client.list_vehicles(limit=1) + if not list_response.results: + pytest.skip("No vehicles available to test list_vehicle_orders") + + vehicle_uuid = ( + list_response.results[0].get("uuid") + if isinstance(list_response.results[0], dict) + else list_response.results[0].uuid + ) + assert vehicle_uuid is not None, "Vehicle UUID should be present" + + response = production_client.list_vehicle_orders(vehicle_uuid, limit=5) + validate_pagination(response) + assert response.count >= 0, "Count should be non-negative" + # ============================================================================ # OTA Endpoints # ============================================================================ diff --git a/tests/test_client.py b/tests/test_client.py index 2bc9004..df948cf 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -435,10 +435,7 @@ def test_get_itdashboard_investment_by_uii(self, mock_request): call_args = mock_request.call_args assert call_args[1]["url"].endswith("/api/itdashboard/021-000000001/") - assert ( - call_args[1]["params"]["shape"] - == ShapeConfig.ITDASHBOARD_INVESTMENTS_COMPREHENSIVE - ) + assert call_args[1]["params"]["shape"] == ShapeConfig.ITDASHBOARD_INVESTMENTS_COMPREHENSIVE assert investment["uii"] == "021-000000001" assert investment["agency_code"] == 21 @@ -459,9 +456,7 @@ def test_list_itdashboard_investments_funding_expansion(self, mock_request): "fy2024_internal_funding": "1000000.00", "fy2024_contribution": "50000.00", }, - "cio_evaluation": [ - {"cioRating": "3 - Medium Risk", "latestIndicator": "Y"} - ], + "cio_evaluation": [{"cioRating": "3 - Medium Risk", "latestIndicator": "Y"}], } ], } diff --git a/uv.lock b/uv.lock index 0edfd6b..7436a2e 100644 --- a/uv.lock +++ b/uv.lock @@ -1831,7 +1831,7 @@ wheels = [ [[package]] name = "tango-python" -version = "0.5.0" +version = "0.6.0" source = { editable = "." } dependencies = [ { name = "httpx" },