Skip to content

SBA OHA on protests: naics_code filter, five OHA fields, conformance mapping#50

Open
makegov-mark[bot] wants to merge 1 commit into
mainfrom
feat/protests-sba-oha
Open

SBA OHA on protests: naics_code filter, five OHA fields, conformance mapping#50
makegov-mark[bot] wants to merge 1 commit into
mainfrom
feat/protests-sba-oha

Conversation

@makegov-mark

@makegov-mark makegov-mark Bot commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

SDK parity for SBA OHA on the protests endpoint.

What

The Tango API now serves SBA OHA (the Small Business Administration's Office of Hearings and Appeals) decisions on /api/protests/ as a third source_system, alongside GAO and the Court of Federal Claims. Three things follow:

  • list_protests(naics_code=...) — the new API filter. It is sent verbatim, not remapped onto naics the way list_contracts() and list_opportunities() remap the identically-named kwarg. The protests API param really is naics_code. There is a test for both halves of that.
  • Five fields on Protest, PROTEST_SCHEMA, and PROTEST_DOCKET_SCHEMAchallenged_party, naics_code, size_standard, outcome_reason, judge. Opt-in via shape= and ungated by tier, so they belong in the schemas but deliberately not in PROTESTS_MINIMAL (they're null on GAO/CoFC rows, which is the large majority of the table).
  • protests mapped in the conformance checker's RESOURCE_TO_METHOD.

Why the gap existed at all

protests had no entry in RESOURCE_TO_METHOD, so it fell through to None and the checker emitted protests: no SDK method implemented for this resourceeven though list_protests has existed all along. Its filters were never validated against the contract. That is precisely how naics_code slipped through, and the identical blind spot exists in tango-node (fixed in its companion PR, makegov/tango-node#12).

With the mapping in place the gate is real, not decorative. Verified by removing the kwarg:

"errors": [
  "protests: `list_protests` missing runtime filters: naics_code"

and restoring it returns errors: [] with the protests warning gone.

The vendored contract

Refreshed via scripts/refresh_contract.py (the sanctioned path). Beyond protests.naics_code, that picks up drift accumulated since it was last vendored: opportunities' active, two max_page_size: null → 100 corrections, NoticeSearchFilter / OpportunityPlaceOfPerformanceFilter class renames, and a name filter.

I kept the full refresh rather than cherry-picking naics_code: it is a generated file, and hand-editing it would leave it internally inconsistent with its own source. None of the extra drift produces a conformance error — the baseline absorbs the known gaps.

Testing

  • 307 unit tests pass; mypy tango/ clean.
  • New TestListProtestsNaicsCodeFilternaics_code reaches the wire as naics_code, naics is absent, and an omitted filter sends no key at all.
  • New TestProtestOhaShapeFields — the five fields parse off a shaped response into the dynamic model.
  • Conformance: errors: [], and protests no longer appears in the warnings.
  • ruff check / ruff format clean on every file this PR touches. (21 pre-existing ruff errors elsewhere in the repo are untouched.)

Risks

Additive only. Protest is a schema dataclass with defaults, so new optional fields break no constructor. No existing kwarg changed meaning.

Merge once SBA OHA is live on the production API — until then ?source_system=sba_oha returns nothing and naics_code matches nothing, so the SDK is correct but the data is not yet visible.

🤖 Generated with Claude Code

The Tango API now serves SBA OHA (Office of Hearings and Appeals) decisions
on /api/protests/ as a third source_system, alongside GAO and the Court of
Federal Claims. This brings the SDK to parity.

list_protests() gains a naics_code filter. It is sent verbatim, NOT remapped
onto `naics` the way list_contracts() and list_opportunities() remap the same
kwarg — the protests API param is literally naics_code.

Protest, PROTEST_SCHEMA, and PROTEST_DOCKET_SCHEMA gain challenged_party,
naics_code, size_standard, outcome_reason, and judge. All five are opt-in via
shape= (null on gao/cofc rows) and ungated by tier, so they belong in the
schemas but not in PROTESTS_MINIMAL.

Also maps `protests` in the conformance checker's RESOURCE_TO_METHOD. It had
no entry, so the resource fell through to "no SDK method implemented" and its
filters were never validated against the contract — which is precisely how the
naics_code gap survived. Verified the gate now bites: removing the kwarg
produces "protests: `list_protests` missing runtime filters: naics_code".

Refreshed the vendored contract via scripts/refresh_contract.py. That also
picks up unrelated drift accumulated since it was last vendored (opportunities
`active`, max_page_size, two filter-class renames) — it is a generated file
and cherry-picking would leave it internally inconsistent.

307 unit tests pass; mypy clean.
@vdavez
vdavez force-pushed the feat/protests-sba-oha branch from 2f9e2b1 to 8d0e91d Compare July 9, 2026 17:45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant