Skip to content

fix(shapes): accept doc_role and the post-launch SLED fields the API serves - #66

Merged
vdavez merged 1 commit into
mainfrom
fix/api-parity-5.0
Sep 23, 2026
Merged

vdavez merged 1 commit into
mainfrom
fix/api-parity-5.0

Conversation

@makegov-mark

@makegov-mark makegov-mark Bot commented Sep 23, 2026

Copy link
Copy Markdown
Contributor

What

Brings the SDK's shape schema up to what the Tango API serves today.
Four fields the API returns were rejected client-side with ShapeValidationError, after the request had already been made:

  • attachments(doc_role, doc_role_alt) on opportunities and notices (Pro plan and above).
  • delisted_at and meta(jurisdiction_declared) on SLED solicitations.

Changes

  • Re-vendored contracts/filter_shape_contract.json (Tango API 5.1.0) with scripts/refresh_contract.py and regenerated tango/shapes/generated_overlay.py with scripts/generate_shape_overlay.py. Neither file is hand-edited.
  • Fixed the overlay generator. Re-vendoring alone did not make doc_role validate on opportunities. An opportunity is reachable from its own resource and as the opportunity expand on vehicles, and the generator let whichever tree it walked last replace the other; the vehicles embed exposes fewer attachment fields, so it hid fields the opportunity endpoint serves. The generator now adds the missing fields instead of replacing the schema, and the later tree still decides every field both define. I diffed every resolved schema path against main: outside SLED, no existing field's type or list-ness changed; the only additions are the four fields above.
  • Added observed types for the SLED resources to contracts/observed_shape_types.json. The type probe predates SLED, so the overlay had guessed SLED types from field names. The entries come from sampling the live API, cross-checked against the API's model definitions where the sample misread a value (a numeric-looking solicitation number, an all-null delisted_at). This fixes the mismatches: meta(last_change_source_declared) is a boolean, attachments(size_bytes, pages) are integers, category_codes is a list of objects, and the date fields parse to datetime rather than a truncated date or a string.
  • delisted_at joins both SLED default shapes, matching the API's own list default, and is documented on SledOpportunity along with meta.jurisdiction_declared.
  • Protest docs: three venues (GAO, COFC, SBA OHA) in docs/API_REFERENCE.md; naics_code is the NAICS code at issue in an SBA OHA appeal, not the solicitation's; case_number examples cover each venue; naics_code added to the documented filters.
  • Document roles documented on Opportunity / Notice and in docs/API_REFERENCE.md: the six values, that they must be named, and that an unclassified attachment omits the keys.
  • Shape-coverage baseline gains contract_appeals and ebuy/requests as unmapped resources, since the SDK does not wrap either yet.

Behavior change worth reading

SLED values now carry the API's types. posted_date, response_deadline, response_deadline_original and bid_opening_date parse to timezone-aware datetime values, where before two were truncated to date and two stayed strings. Code comparing them as strings needs to compare datetime values. This is in the changelog under Changed.

Relationship to other PRs

Not done here

  • verbose on list_sled_opportunities(): the API reads it off the request rather than its filter set, so it is absent from the contract and the conformance check would flag it as a stale param. It also only matters when no shape is sent.
  • Shared nested schemas remain permissive: because the vehicles embed resolves to the same Opportunity schema, the SDK accepts doc_role through vehicles(opportunity(attachments(...))), which the API rejects. That was already true of extracted_text.

Verification

  • New tests: tests/test_opportunity_attachments.py (shape validation plus list and detail round-trips for opportunities and notices) and additions to tests/test_sled.py (the two SLED fields validate, delisted_at is in the default list shape and parses to a datetime, detail fields keep the API's types).
  • Against main's code, 10 of the new tests fail. With only the generator fix reverted, the 4 opportunity tests fail. With only the SLED observations reverted, the type test fails.
  • Full suite: 511 passed, 31 skipped. ruff format --check tango/, ruff check, and strict mypy tango/ are clean. Filter/shape conformance: 0 errors. Shape coverage: 0 new gaps.

🤖 Generated with Claude Code

…serves

Shapes naming `attachments(doc_role, doc_role_alt)` on opportunities and notices, or `delisted_at` / `meta(jurisdiction_declared)` on SLED solicitations, raised `ShapeValidationError` after the request had already been made, although the API returns all four.

Re-vendors the filter/shape contract (Tango API 5.1.0) and regenerates the shape overlay from it.
Re-vendoring alone did not fix opportunities: the overlay generator let the vehicles resource's `opportunity` embed, which exposes fewer attachment fields, replace the opportunity resource's own `attachments` schema.
The generator now adds the missing fields instead of replacing the schema, and keeps every existing field's type.

Adds observed types for the SLED resources, which the type probe predates, so the overlay stops guessing them from field names: the date fields parse to `datetime`, `category_codes` / `attachments` / `revisions` are lists, and the flags, sizes and counts are booleans and integers.

Adds `delisted_at` to both SLED default shapes, matching the API's list default, and documents the new fields.
Corrects the protest docstrings and API reference: three venues (GAO, COFC, SBA OHA), `naics_code` is the SBA OHA code at issue, and `case_number` examples cover each venue.

Baselines `contract_appeals` and `ebuy/requests` as unmapped resources in the shape-coverage gate.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@vdavez
vdavez marked this pull request as ready for review September 23, 2026 13:24
@vdavez
vdavez merged commit 3004f73 into main Sep 23, 2026
11 checks passed
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