From d0cf14f08231e49b97117f76093854e6b554fdd6 Mon Sep 17 00:00:00 2001 From: "V. David Zvenyach" Date: Mon, 10 Aug 2026 09:35:09 -0500 Subject: [PATCH 1/2] docs(entities): document the pipe-OR syntax `socioeconomic` accepts The server has always ORed pipe-separated values (`socioeconomic="8A|WOSB"`), but neither the docstring nor the API reference said so, and no test pinned the verbatim passthrough. A user analyzing awards to Black-owned businesses hit this gap while trying to filter entities by socioeconomic designation. Co-Authored-By: Claude Fable 5 --- CHANGELOG.md | 3 +++ docs/API_REFERENCE.md | 2 +- tango/client.py | 2 +- tests/test_client.py | 16 ++++++++++++++++ 4 files changed, 21 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c562710..df19cf6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Documentation +- Documented (and pinned with tests) the OR syntax `list_entities(socioeconomic=...)` has always supported: pipe-separated values match entities holding any of the listed designations, e.g. `socioeconomic="8A|WOSB"`. + ## [1.5.0] - 2026-08-04 ### Added diff --git a/docs/API_REFERENCE.md b/docs/API_REFERENCE.md index e7c7560..f1c1dbd 100644 --- a/docs/API_REFERENCE.md +++ b/docs/API_REFERENCE.md @@ -713,7 +713,7 @@ entities = client.list_entities( - `name` - Filter by entity name - `psc` - Filter by PSC code - `purpose_of_registration_code` - Filter by registration purpose -- `socioeconomic` - Filter by socioeconomic status +- `socioeconomic` - Filter by socioeconomic status; accepts pipe-separated values for OR semantics, e.g. `socioeconomic="8A|WOSB"` - `state` - Filter by state - `total_awards_obligated_gte` / `total_awards_obligated_lte` - Obligation amount range - `uei` - Filter by UEI diff --git a/tango/client.py b/tango/client.py index 29d9f1b..699e1db 100644 --- a/tango/client.py +++ b/tango/client.py @@ -2081,7 +2081,7 @@ def list_entities( name: Entity name filter psc: PSC code filter purpose_of_registration_code: Purpose of registration code - socioeconomic: Socioeconomic status filter + socioeconomic: Socioeconomic status filter; accepts pipe-separated values for OR semantics, e.g. ``socioeconomic="8A|WOSB"`` state: State filter total_awards_obligated_gte: Total awards obligated >= total_awards_obligated_lte: Total awards obligated <= diff --git a/tests/test_client.py b/tests/test_client.py index 737c1b1..74cbf24 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -1992,6 +1992,22 @@ def test_list_psc_omits_unset_has_awards(self, mock_request): assert "has_awards" not in mock_request.call_args[1]["params"] +class TestEntityFilters: + """The server ORs pipe-separated `socioeconomic` values; the SDK's job is to forward the string verbatim.""" + + @patch("tango.client.httpx.Client.request") + def test_list_entities_forwards_pipe_separated_socioeconomic(self, mock_request): + _stub_empty_page(mock_request) + TangoClient(api_key="test-key").list_entities(socioeconomic="8A|WOSB") + assert mock_request.call_args[1]["params"]["socioeconomic"] == "8A|WOSB" + + @patch("tango.client.httpx.Client.request") + def test_list_entities_omits_unset_socioeconomic(self, mock_request): + _stub_empty_page(mock_request) + TangoClient(api_key="test-key").list_entities() + assert "socioeconomic" not in mock_request.call_args[1]["params"] + + class TestAgencyFilterDiagnostics: """`meta` from the API's agency-filter diagnostics. From a00a62f8c291170b0061328730cbd1b55bbdad8c Mon Sep 17 00:00:00 2001 From: "V. David Zvenyach" Date: Mon, 10 Aug 2026 10:58:06 -0500 Subject: [PATCH 2/2] docs(entities): use real SAM business-type codes in the socioeconomic examples Live testing showed the example codes were the wrong vocabulary: the filter matches SAM business-type codes, and set-aside-style values like "8A" or "WOSB" silently match zero rows. Examples now use real codes ("OY" Black American Owned, "A2" Woman Owned), the docs name the vocabulary and point at GET /api/business_types/, and the passthrough tests pin the corrected value. Co-Authored-By: Claude Fable 5 --- CHANGELOG.md | 2 +- docs/API_REFERENCE.md | 2 +- tango/client.py | 2 +- tests/test_client.py | 4 ++-- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index df19cf6..a485161 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,7 +8,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] ### Documentation -- Documented (and pinned with tests) the OR syntax `list_entities(socioeconomic=...)` has always supported: pipe-separated values match entities holding any of the listed designations, e.g. `socioeconomic="8A|WOSB"`. +- Documented (and pinned with tests) the OR syntax `list_entities(socioeconomic=...)` has always supported: pipe-separated values match entities holding any of the listed designations, e.g. `socioeconomic="OY|A2"`; values are SAM business-type codes (e.g. `OY` Black American Owned, `A6` SBA-certified 8(a), `A2` Woman Owned), not set-aside codes. ## [1.5.0] - 2026-08-04 diff --git a/docs/API_REFERENCE.md b/docs/API_REFERENCE.md index f1c1dbd..a93f1d8 100644 --- a/docs/API_REFERENCE.md +++ b/docs/API_REFERENCE.md @@ -713,7 +713,7 @@ entities = client.list_entities( - `name` - Filter by entity name - `psc` - Filter by PSC code - `purpose_of_registration_code` - Filter by registration purpose -- `socioeconomic` - Filter by socioeconomic status; accepts pipe-separated values for OR semantics, e.g. `socioeconomic="8A|WOSB"` +- `socioeconomic` - Filter by socioeconomic status; takes SAM business-type codes (e.g. `OY` Black American Owned, `A6` SBA-certified 8(a), `A2` Woman Owned — see `GET /api/business_types/`), not set-aside codes; accepts pipe-separated values for OR semantics, e.g. `socioeconomic="OY|A2"` - `state` - Filter by state - `total_awards_obligated_gte` / `total_awards_obligated_lte` - Obligation amount range - `uei` - Filter by UEI diff --git a/tango/client.py b/tango/client.py index 699e1db..dbfbe9f 100644 --- a/tango/client.py +++ b/tango/client.py @@ -2081,7 +2081,7 @@ def list_entities( name: Entity name filter psc: PSC code filter purpose_of_registration_code: Purpose of registration code - socioeconomic: Socioeconomic status filter; accepts pipe-separated values for OR semantics, e.g. ``socioeconomic="8A|WOSB"`` + socioeconomic: Socioeconomic status filter; takes SAM business-type codes (e.g. "OY" Black American Owned, "A6" SBA-certified 8(a), "A2" Woman Owned — see `GET /api/business_types/`), not set-aside codes, and accepts pipe-separated values for OR semantics, e.g. ``socioeconomic="OY|A2"`` state: State filter total_awards_obligated_gte: Total awards obligated >= total_awards_obligated_lte: Total awards obligated <= diff --git a/tests/test_client.py b/tests/test_client.py index 74cbf24..3ce0f1b 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -1998,8 +1998,8 @@ class TestEntityFilters: @patch("tango.client.httpx.Client.request") def test_list_entities_forwards_pipe_separated_socioeconomic(self, mock_request): _stub_empty_page(mock_request) - TangoClient(api_key="test-key").list_entities(socioeconomic="8A|WOSB") - assert mock_request.call_args[1]["params"]["socioeconomic"] == "8A|WOSB" + TangoClient(api_key="test-key").list_entities(socioeconomic="OY|A2") + assert mock_request.call_args[1]["params"]["socioeconomic"] == "OY|A2" @patch("tango.client.httpx.Client.request") def test_list_entities_omits_unset_socioeconomic(self, mock_request):