diff --git a/CHANGELOG.md b/CHANGELOG.md index 2360209..6a3d8db 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Added + +- **`list_sbir_topics(cycle_name=...)`** filters topics by their DSIP solicitation cycle (e.g. `DOD_SBIR_2026_P1_CBZ`), and `SbirTopic` gains a `cycle_name` field (Tango API 5.3.1). The vendored API contract is refreshed to 5.3.1. + ## [1.9.0] - 2026-09-28 ### Added diff --git a/contracts/filter_shape_contract.json b/contracts/filter_shape_contract.json index a1fe295..3e0f0ca 100644 --- a/contracts/filter_shape_contract.json +++ b/contracts/filter_shape_contract.json @@ -1,6 +1,6 @@ { "meta": { - "api_version": "5.3.0", + "api_version": "5.3.1", "description": "Canonical API filter/shape contract. Downstream consumers (SDK, MCP) should validate their conformance against this manifest.", "generated_from": "scripts/filter_shape_conformance.py", "schema_version": 2 @@ -13990,6 +13990,7 @@ "agency", "close_date_after", "close_date_before", + "cycle_name", "doc_source", "open_date_after", "open_date_before", @@ -14025,6 +14026,10 @@ "lookup": "lte", "type": "date" }, + "cycle_name": { + "filter_class": "CharFilter", + "type": "string" + }, "doc_source": { "filter_class": "CharFilter", "lookup": "iexact", @@ -14134,6 +14139,7 @@ "activity", "agency", "close_date", + "cycle_name", "description", "doc_source", "due_dates_text", @@ -14156,6 +14162,7 @@ "activity", "agency", "close_date", + "cycle_name", "description", "doc_source", "due_dates_text", diff --git a/tango/client.py b/tango/client.py index 64854d1..9691e5d 100644 --- a/tango/client.py +++ b/tango/client.py @@ -3733,6 +3733,7 @@ def list_sbir_topics( flat_lists: bool = False, topic_number: str | None = None, solicitation_number: str | None = None, + cycle_name: str | None = None, agency: str | None = None, activity: str | None = None, year: int | None = None, @@ -3759,6 +3760,7 @@ def list_sbir_topics( flat_lists: If True, flatten arrays using indexed keys topic_number: Filter by topic number solicitation_number: Filter by solicitation number + cycle_name: Filter by DSIP solicitation cycle, exact (e.g. "DOD_SBIR_2026_P1_CBZ"; requires Tango API 5.3.1) agency: Filter by agency activity: Filter by activity (component/branch) year: Filter by solicitation year @@ -3786,6 +3788,7 @@ def list_sbir_topics( for key, val in ( ("topic_number", topic_number), ("solicitation_number", solicitation_number), + ("cycle_name", cycle_name), ("agency", agency), ("activity", activity), ("year", year), diff --git a/tango/models.py b/tango/models.py index 56f0e6c..b1097e9 100644 --- a/tango/models.py +++ b/tango/models.py @@ -1000,6 +1000,7 @@ class SbirTopic: activity: str | None = None year: int | None = None solicitation_number: str | None = None + cycle_name: str | None = None solicitation_status: str | None = None release_date: str | None = None open_date: str | None = None diff --git a/tango/shapes/generated_overlay.py b/tango/shapes/generated_overlay.py index c781166..eddbccd 100644 --- a/tango/shapes/generated_overlay.py +++ b/tango/shapes/generated_overlay.py @@ -2731,6 +2731,7 @@ "activity": FieldSchema(name="activity", type=str, is_optional=True, is_list=False), "agency": FieldSchema(name="agency", type=str, is_optional=True, is_list=False), "close_date": FieldSchema(name="close_date", type=date, is_optional=True, is_list=False), + "cycle_name": FieldSchema(name="cycle_name", type=str, is_optional=True, is_list=False), "description": FieldSchema(name="description", type=str, is_optional=True, is_list=False), "doc_source": FieldSchema(name="doc_source", type=str, is_optional=True, is_list=False), "due_dates_text": FieldSchema( diff --git a/tests/test_dibbs_exclusions_sbir.py b/tests/test_dibbs_exclusions_sbir.py index c9edf54..a6ce47f 100644 --- a/tests/test_dibbs_exclusions_sbir.py +++ b/tests/test_dibbs_exclusions_sbir.py @@ -141,6 +141,12 @@ def test_list_sbir_topics_filters(self, mock_request): assert params["year"] == 2026 assert params["topic_number"] == "A26-001" + @patch("tango.client.httpx.Client.request") + def test_list_sbir_topics_cycle_name(self, mock_request): + _mock(mock_request) + TangoClient(api_key="k").list_sbir_topics(cycle_name="DOD_SBIR_2026_P1_CBZ") + assert _call_params(mock_request)["cycle_name"] == "DOD_SBIR_2026_P1_CBZ" + @patch("tango.client.httpx.Client.request") def test_get_sbir_topic_uses_topic_id_route(self, mock_request): _mock(mock_request, {"topic_id": "t1"})