Skip to content

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

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#12
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

SDK parity for SBA OHA on the protests endpoint. Mirrors makegov/tango-python#50.

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:

  • ListProtestsOptions.naics_code — the new API filter. It is sent verbatim, not remapped onto naics the way listContracts() and listOpportunities() remap the identically-named option. The protests API param really is naics_code. The new client test asserts both halves: naics_code=541512 present, naics absent.
  • Five fields on ProtestRecord, PROTEST_SCHEMA, and PROTEST_DOCKET_SCHEMAchallenged_party, naics_code, size_standard, outcome_reason, judge. Opt-in via shape= and ungated by tier. ProtestRecord carries an index signature, so these already parsed at runtime; declaring them is what gives consumers types instead of unknown.
  • protests mapped in the conformance script's RESOURCE_TO_METHOD.

Why the gap existed at all

protests had no entry in RESOURCE_TO_METHOD, so the checker reported protests: no SDK method mapped in conformance scripteven though listProtests has existed all along — and its filters were never validated against the contract. The Python SDK had the identical blind spot, which is why the same gap survived in both at once.

With the mapping in place the checker sees it. Verified by removing the option:

"protests: `listProtests` relies on index signature for filters: naics_code"

Worth being precise about the strength of this gate: because ListProtestsOptions has [key: string]: unknown, a missing filter is a warning, not an error — unlike the Python side, where it is a hard error. The index signature means the call would still have worked; what was missing was the type and the discoverability.

Schema parity counts

tests/unit/shapes.schema.parity.test.ts pins field counts against tango-python's explicit_schemas.py. Those move 18 → 23 (case) and 16 → 21 (docket). I read the real counts off both SDKs rather than assuming the delta:

python PROTEST_SCHEMA: 23   node PROTEST_SCHEMA: 23
python PROTEST_DOCKET_SCHEMA: 21   node PROTEST_DOCKET_SCHEMA: 21

Added assertions that the five OHA fields are optional str scalars with no nested model, and that they are present in the docket schema too (they're servable inside dockets(...)).

Testing

  • 222 tests pass (23 files). npm run typecheck, npm run lint, and npm run build all clean.
  • Conformance against the real manifest: errors=0, and protests no longer appears in the warnings.
  • Three touched files fail prettier --check: src/client.ts, scripts/check-filter-shape-conformance.ts, tests/unit/shapes.schema.parity.test.ts. All three already fail on origin/main — verified in a clean git worktree so the repo's prettier config actually applied, rather than by piping through stdin. This PR adds no new formatting debt, and I did not reformat them, since that would bury the real diff.

Risks

Additive only. Every new field is optional, and ProtestRecord's index signature meant they already flowed through untyped. No existing option 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, mirroring the tango-python
change of the same name.

ListProtestsOptions gains naics_code. It is sent verbatim, NOT remapped onto
`naics` the way listContracts() and listOpportunities() remap the same option —
the protests API param is literally naics_code. The new client test asserts
both halves of that.

ProtestRecord, PROTEST_SCHEMA, and PROTEST_DOCKET_SCHEMA gain challenged_party,
naics_code, size_standard, outcome_reason, and judge. ProtestRecord has an index
signature, so these already parsed at runtime — declaring them is what gives
consumers types instead of `unknown`.

The schema parity counts move 18 -> 23 (case) and 16 -> 21 (docket), verified
against tango-python's explicit_schemas.py rather than assumed.

Also maps `protests` in RESOURCE_TO_METHOD. It had no entry, so the resource
was reported as "no SDK method mapped" and its filters were never validated
against the contract — the same blind spot as the Python SDK, which is why the
gap survived in both. Verified the checker now sees it: removing naics_code
reports "protests: `listProtests` relies on index signature for filters:
naics_code".

222 tests pass; typecheck, eslint, and build clean. The three prettier warnings
on touched files pre-exist on origin/main (verified in a clean worktree).
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