From 2f564cb0e4376970a978b2f4e8a0d84845582dc9 Mon Sep 17 00:00:00 2001 From: "V. David Zvenyach" Date: Tue, 6 Oct 2026 17:11:18 -0500 Subject: [PATCH] feat: Tango API 5.9.0 contract, Federal Register documents, attachment file and link counts Re-vendors the API contract from 5.1.0 to Tango API 5.9.0 and regenerates the shape overlay, so shapes accept every field the API added in between. Opportunities accept `meta(files_count,links_count)` and notices accept `file_count` and `link_count`, both new in 5.9.0; the types and docs say what they count and that null means not yet counted, not zero. Adds `listFederalRegisterDocuments` and `getFederalRegisterDocument` with all seventeen filters typed, a `FederalRegisterDocument` model, a shape schema and two default shapes, matching tango-python. Types the filters the API added on resources the SDK already wraps: `awarded` and `awardee_uei` on opportunities, `agency` on budget accounts, exclusions and IT Dashboard, `cycle_name` on SBIR topics, and the account category and `data_through_period` filters on budget accounts. Re-vendors the observed shape types from tango-python so the new fields carry the types the API returns. Both conformance gates pass against the new contract with no baseline growth; eBuy requests remain the one baselined resource without a method. Co-Authored-By: Claude Opus 5.5 --- CHANGELOG.md | 18 + README.md | 3 +- contracts/filter_shape_contract.json | 492 +++++++++- contracts/observed_shape_types.json | 1002 ++++++++++++++++++-- docs/API_REFERENCE.md | 89 +- docs/SHAPES.md | 2 + scripts/check-filter-shape-conformance.ts | 1 + scripts/check-shape-coverage.ts | 1 + src/client.ts | 100 ++ src/config.ts | 16 + src/index.ts | 1 + src/models/FederalRegister.ts | 60 ++ src/models/Notice.ts | 6 + src/models/Opportunity.ts | 18 + src/models/index.ts | 3 +- src/shapes/explicitSchemas.ts | 314 ++++++ src/shapes/generatedOverlay.ts | 43 + tests/unit/client.federal-register.test.ts | 189 ++++ tests/unit/client.shape-parity.test.ts | 76 ++ tests/unit/client.typed-filters.test.ts | 91 ++ 20 files changed, 2420 insertions(+), 105 deletions(-) create mode 100644 src/models/FederalRegister.ts create mode 100644 tests/unit/client.federal-register.test.ts create mode 100644 tests/unit/client.typed-filters.test.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 3891661..7c27ccf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,22 @@ This project follows [Semantic Versioning](https://semver.org/). ### Added +- **Attachment file and link counts on opportunities and notices** (Tango API 5.9.0; parity with tango-python). Opportunities accept `meta(files_count,links_count)` beside `meta(attachments_count)`, and notices accept `file_count` and `link_count` beside `attachment_count`. An attachment's `type` is `file` (a document; only a file has extracted text) or `link` (a URL the notice lists). The new fields count each, and the existing total still counts every attachment, links included. An attachment of any other type counts only in the total, so files plus links need not equal it. The new fields are `null` until a record has been counted, so read `null` as unknown, not zero. `Opportunity` gains a typed `meta` (the new `OpportunityMeta`), `Notice` gains the three counts, and `docs/API_REFERENCE.md` documents the rules. The SDK's default shapes are unchanged, so name the fields in `shape`. + +- **Federal Register documents** (Tango API 5.3.0; parity with tango-python). `listFederalRegisterDocuments(options)` and `getFederalRegisterDocument(uuid, options)` over `/api/federal_register/`, plus a `FederalRegisterDocument` model interface, a registered shape schema, and the `FEDERAL_REGISTER_MINIMAL` / `FEDERAL_REGISTER_COMPREHENSIVE` defaults. These are the rules, proposed rules, notices and presidential documents published in the Federal Register since 1994. All seventeen of the API's filters are typed options on `ListFederalRegisterDocumentsOptions`, including `comments_open`, `cfr_title` / `cfr_part`, `rin`, and both `agency` (a Tango organization, including its sub-agencies) and `fr_agency` (the Federal Register's own agency slug). + + `getFederalRegisterDocument()` takes the document's `uuid`, not its `document_number`, because a document number is not unique on its own: the Federal Register reused some before 2016. Look a document up by number with `listFederalRegisterDocuments({ document_number })`. `full_text` is available on the detail method only when named in `shape`, and neither default shape includes it, since it can run to several MB. `ordering: "rank"` requires a non-empty `search`. Documents published before 2008 are mostly typed `Uncategorized Document`, so a `type` filter undercounts that era. + +- **Award fields on opportunities and notices** (Tango API 5.5.0; parity with tango-python). `ListOpportunitiesOptions` types the `awarded` and `awardee_uei` filters, both of which search every opportunity rather than only active ones. Opportunity shapes accept `awarded`, `award_count`, `award_date`, `award_amount`, `awardee`, `awardee_uei`, `solicitation_opportunity_id` and an `awards(...)` expand (a list, with `award_number`, `award_date`, `award_amount`, `awardee`, `awardee_uei`, `notice_id`, `opportunity_id`); notice shapes accept `award_date`, `award_amount`, `awardee` and `awardee_uei`. + +- **`agency` filter on `listBudgetAccounts()`, `listExclusions()` and `listItDashboard()`** (Tango API 5.3.0; parity with tango-python). It takes a Tango agency name, abbreviation, code or organization key (e.g. `EPA`) and matches the whole organization, so a department includes its sub-agencies; OR several with `|`. On exclusions it scopes by the excluding agency. On IT Dashboard it is available at every plan. + +- **`cycle_name` filter on `listSbirTopics()`** (Tango API 5.3.1), an exact match on the DSIP solicitation cycle such as `DOD_SBIR_2026_P1_CBZ`. SBIR topic shapes accept `cycle_name`. + +- **Budget account filters `account_category`, `account_category__in`, `data_through_period`, `data_through_period__gte`, `data_through_period__lte` and `data_through_period__isnull`** (Tango API 5.7.0 and 5.8.0) are typed options on `ListBudgetAccountsOptions`, sent verbatim under their dunder names. `data_through_period` is the File A fiscal period (1-12) an account-year's figures run through, so a year below 12 is partial. + +- **Shapes accept the fields the API added through 5.9.0.** The `organization(...)` expand on budget accounts, exclusions, SBIR topics and SBIR solicitations (with `organization_id` on the first two); `account_category`, `data_through_period` and `source_anomalies` on budget accounts; and `holder_count` and `order_winner_count` on vehicles. The SDK rejected these client-side before. Default shapes are unchanged. + - **`matched_by` on agency-filter diagnostics** (Tango API 5.9.0; parity with tango-python). Each entry of `PaginatedResponse.meta.resolved_filters[]` that resolved now says how its token matched: `key` (an organization UUID), `code` (a 3-digit CGAC or 4-digit FPDS code), `name` (the organization's name, including a department's everyday name, a spelling variant or a rename), `alias` (an abbreviation or the organization's own alias) or `fuzzy` (a looser text match, worth checking against the resolved name). An entry that did not resolve has no `matched_by`. `meta` is passed through as the API sends it, so no code change was needed to receive the field; it is now documented on the interface and in `docs/API_REFERENCE.md` and pinned by a test. `agencyWarnings`, `unresolvedAgencyTokens` and `resolvedAgencies` are unchanged. - **Boards-of-contract-appeals decisions** (Tango API 4.26.0). `listContractAppeals(options)` and `getContractAppeal(uuid, options)` over `/api/contract_appeals/`, with every filter the API accepts declared as a typed option on `ListContractAppealsOptions` (`search`, `board`, `docket`, `appellant`, `judge`, `decision_type`, the `decision_date_after` / `_before` pair, `listed`, `document_id`, `ordering`), the new `ContractAppealRecord` return type, and a registered `ContractAppeal` shape schema so the typed shape API resolves the resource's fields. @@ -30,6 +46,7 @@ This project follows [Semantic Versioning](https://semver.org/). ### Changed +- Re-vendored the contract for Tango API 5.9.0 (51 resources) and regenerated the overlay: 385 fields across 28 containers, 76 nested schemas. `contracts/observed_shape_types.json` is re-vendored from tango-python as well, so the new fields carry the types the API returns (`awards` is a list, `awarded` a boolean, `data_through_period` an integer) instead of types guessed from their names. With Federal Register documents wrapped, eBuy requests are the only resource in the contract without an SDK method, and they stay baselined as a tracked gap. - Re-vendored `contracts/filter_shape_contract.json` (schema_version 2, 48 resources) and regenerated `src/shapes/generatedOverlay.ts` from it — 359 fields across 25 containers, 73 nested schemas. - Re-vendored the contract for Tango API 5.1.0 and regenerated the overlay, which now merges a model's expand when two resources embed it instead of letting the narrower copy win. eBuy requests are in the contract without an SDK method yet, so it is baselined as a tracked gap. - Baselined 14 reverse-shape-coverage gaps in `contracts/shape_coverage_baseline.json`, matching tango-python. All 14 are the nested sub-resource routes above, which reuse the parent resource's model rather than carrying one of their own; none is SLED, and none is a regression — they became visible only with the re-vendored contract. @@ -43,6 +60,7 @@ This project follows [Semantic Versioning](https://semver.org/). ### Documentation +- New **Federal Register** section in `docs/API_REFERENCE.md` with both methods and the full filter table, and both new constants in the `docs/SHAPES.md` preset table. `README.md`'s method list gained both methods. - New **Contract Appeals** section in `docs/API_REFERENCE.md` covering both methods, the full filter table, and the two properties that catch people out (the core-subset default and the tier-gated, absent-rather-than-null `decision_text`). `README.md`'s method list gained both methods. - New **State & Local (SLED) — Beta** section in `docs/API_REFERENCE.md` covering all six methods, both defaults that surprise people, and the new `ShapeConfig` constants. - `docs/WEBHOOKS.md` troubleshooting gained the date-lapse rule and its one exception. An exclusion or a DIBBS solicitation reaching its date fires nothing, because open/closed is derived at query time — but `alerts.sled_opportunity.match` **does** fire on a closing, since SLED liveness is a stored column a fifteen-minute sweep writes. diff --git a/README.md b/README.md index e526bcf..3cab249 100644 --- a/README.md +++ b/README.md @@ -203,11 +203,12 @@ The Node.js client mirrors the Python SDK's high-level API. Selected highlights: - `getForecast(id, options)` / `getOpportunity(opportunityId, options)` / `getNotice(noticeId, options)` / `getGrant(grantId, options)` - `searchOpportunityAttachments(options)` -**GSA eLibrary / Protests / Contract Appeals / IT Dashboard / LCATs** +**GSA eLibrary / Protests / Contract Appeals / Federal Register / IT Dashboard / LCATs** - `listGsaElibraryContracts(options)` / `getGsaElibraryContract(uuid, options)` - `listProtests(options)` / `getProtest(caseId)` - `listContractAppeals(options)` / `getContractAppeal(uuid, options)` +- `listFederalRegisterDocuments(options)` / `getFederalRegisterDocument(uuid, options)` - `listItDashboard(options)` / `getItDashboard(uii)` - `listLcats(options)` / `listIdvLcats(key, options)` diff --git a/contracts/filter_shape_contract.json b/contracts/filter_shape_contract.json index 0e47b14..e888e74 100644 --- a/contracts/filter_shape_contract.json +++ b/contracts/filter_shape_contract.json @@ -1,6 +1,6 @@ { "meta": { - "api_version": "5.1.0", + "api_version": "5.9.0", "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 @@ -1628,6 +1628,8 @@ "resource_key": "budget/accounts", "runtime": { "filter_params": [ + "account_category", + "account_category__in", "account_title__icontains", "actual_vs_requested_contract", "actual_vs_requested_contract__gte", @@ -1635,6 +1637,7 @@ "actual_vs_requested_contract_capped", "actual_vs_requested_contract_capped__gte", "actual_vs_requested_contract_capped__lte", + "agency", "agency_code", "agency_code__in", "apportioned", @@ -1669,6 +1672,10 @@ "contract_share_of_obligated_capped", "contract_share_of_obligated_capped__gte", "contract_share_of_obligated_capped__lte", + "data_through_period", + "data_through_period__gte", + "data_through_period__isnull", + "data_through_period__lte", "enacted_ba", "enacted_ba_5yr_cagr", "enacted_ba_5yr_cagr__gte", @@ -1726,6 +1733,15 @@ "unobligated_pct__lte" ], "filter_params_detail": { + "account_category": { + "filter_class": "CharFilter", + "type": "string" + }, + "account_category__in": { + "filter_class": "CharInFilter", + "lookup": "in", + "type": "string" + }, "account_title__icontains": { "filter_class": "CharFilter", "lookup": "icontains", @@ -1759,6 +1775,10 @@ "lookup": "lte", "type": "number" }, + "agency": { + "filter_class": "OrganizationScopeAgencyFilter", + "type": "string" + }, "agency_code": { "filter_class": "CharFilter", "type": "string" @@ -1917,6 +1937,25 @@ "lookup": "lte", "type": "number" }, + "data_through_period": { + "filter_class": "NumberFilter", + "type": "number" + }, + "data_through_period__gte": { + "filter_class": "NumberFilter", + "lookup": "gte", + "type": "number" + }, + "data_through_period__isnull": { + "filter_class": "BooleanFilter", + "lookup": "isnull", + "type": "boolean" + }, + "data_through_period__lte": { + "filter_class": "NumberFilter", + "lookup": "lte", + "type": "number" + }, "enacted_ba": { "filter_class": "NumberFilter", "type": "number" @@ -2240,9 +2279,22 @@ "subaccount_code", "subfunction_code" ] + }, + "organization": { + "expands": {}, + "fields": [ + "agency_code", + "agency_name", + "department_code", + "department_name", + "office_code", + "office_name", + "organization_id" + ] } }, "fields": [ + "account_category", "account_narrative_excerpt", "account_title", "actual_vs_requested_contract", @@ -2275,6 +2327,7 @@ "contract_share_of_obligated", "contract_share_of_obligated_capped", "created", + "data_through_period", "enacted_ba", "enacted_ba_5yr_cagr", "enacted_ba_yoy_pct", @@ -2299,6 +2352,7 @@ "obligated_total", "obligated_yoy_pct", "on_off_budget", + "organization_id", "outlayed_to_obligated_pct", "outlayed_to_obligated_pct_capped", "outlayed_to_obligated_pct_capped_flag", @@ -2306,6 +2360,7 @@ "requested_ba", "requested_contractual_services", "requested_personnel_share", + "source_anomalies", "subfunction_code", "top_contract_recipients", "top_grant_recipients", @@ -2315,6 +2370,7 @@ ] }, "shape_flat_paths": [ + "account_category", "account_narrative_excerpt", "account_title", "actual_vs_requested_contract", @@ -2360,6 +2416,7 @@ "contract_share_of_obligated", "contract_share_of_obligated_capped", "created", + "data_through_period", "enacted_ba", "enacted_ba_5yr_cagr", "enacted_ba_yoy_pct", @@ -2407,6 +2464,15 @@ "obligated_total", "obligated_yoy_pct", "on_off_budget", + "organization", + "organization.agency_code", + "organization.agency_name", + "organization.department_code", + "organization.department_name", + "organization.office_code", + "organization.office_name", + "organization.organization_id", + "organization_id", "outlayed_to_obligated_pct", "outlayed_to_obligated_pct_capped", "outlayed_to_obligated_pct_capped_flag", @@ -2414,6 +2480,7 @@ "requested_ba", "requested_contractual_services", "requested_personnel_share", + "source_anomalies", "subfunction_code", "top_contract_recipients", "top_grant_recipients", @@ -4174,6 +4241,7 @@ "resource_key": "ebuy/requests", "runtime": { "filter_params": [ + "agency", "buyer_agency", "close_date_after", "close_date_before", @@ -4189,6 +4257,10 @@ "status" ], "filter_params_detail": { + "agency": { + "filter_class": "OrganizationScopeAgencyFilter", + "type": "string" + }, "buyer_agency": { "filter_class": "BaseSmartFilter", "type": "string" @@ -4234,7 +4306,7 @@ "type": "string" }, "search": { - "filter_class": "BaseSmartFilter", + "filter_class": "EbuyRequestSearchFilter", "type": "string" }, "sin": { @@ -4461,7 +4533,6 @@ }, "state": { "filter_class": "CharFilter", - "lookup": "state_or_province_code__contains", "type": "string" }, "total_awards_obligated_gte": { @@ -4479,7 +4550,6 @@ }, "zip_code": { "filter_class": "CharFilter", - "lookup": "zip_code__exact", "type": "string" } }, @@ -7363,6 +7433,7 @@ "activate_date_after", "activate_date_before", "active", + "agency", "cage_code", "classification_type", "delisted", @@ -7394,6 +7465,10 @@ "filter_class": "BooleanFilter", "type": "boolean" }, + "agency": { + "filter_class": "OrganizationScopeAgencyFilter", + "type": "string" + }, "cage_code": { "filter_class": "BaseSmartFilter", "type": "string" @@ -7474,7 +7549,20 @@ "max_page_size": 100 }, "shape": { - "expands": {}, + "expands": { + "organization": { + "expands": {}, + "fields": [ + "agency_code", + "agency_name", + "department_code", + "department_name", + "office_code", + "office_name", + "organization_id" + ] + } + }, "fields": [ "activate_date", "additional_comments", @@ -7500,6 +7588,7 @@ "middle_name", "more_locations", "npi", + "organization_id", "prefix", "primary_address", "references", @@ -7542,6 +7631,15 @@ "middle_name", "more_locations", "npi", + "organization", + "organization.agency_code", + "organization.agency_name", + "organization.department_code", + "organization.department_name", + "organization.office_code", + "organization.office_name", + "organization.organization_id", + "organization_id", "prefix", "primary_address", "references", @@ -7575,6 +7673,230 @@ "show_shapes" ] }, + "federal_register": { + "basename": "federalregisterdocument", + "docs_file": null, + "docs_params": [], + "prefix": "federal_register", + "resource_key": "federal_register", + "runtime": { + "filter_params": [ + "agency", + "cfr_part", + "cfr_title", + "comments_close_on_after", + "comments_close_on_before", + "comments_open", + "document_number", + "effective_on_after", + "effective_on_before", + "executive_order_number", + "fr_agency", + "publication_date_after", + "publication_date_before", + "rin", + "search", + "significant", + "type" + ], + "filter_params_detail": { + "agency": { + "filter_class": "FederalRegisterAgencyFilter", + "type": "string" + }, + "cfr_part": { + "filter_class": "CharFilter", + "type": "string" + }, + "cfr_title": { + "filter_class": "CharFilter", + "type": "string" + }, + "comments_close_on_after": { + "filter_class": "DateFilter", + "lookup": "gte", + "type": "date" + }, + "comments_close_on_before": { + "filter_class": "DateFilter", + "lookup": "lte", + "type": "date" + }, + "comments_open": { + "filter_class": "BooleanFilter", + "type": "boolean" + }, + "document_number": { + "filter_class": "IdListFilter", + "type": "string" + }, + "effective_on_after": { + "filter_class": "DateFilter", + "lookup": "gte", + "type": "date" + }, + "effective_on_before": { + "filter_class": "DateFilter", + "lookup": "lte", + "type": "date" + }, + "executive_order_number": { + "filter_class": "CharFilter", + "type": "string" + }, + "fr_agency": { + "filter_class": "JsonbContainsFilter", + "type": "string" + }, + "publication_date_after": { + "filter_class": "DateFilter", + "lookup": "gte", + "type": "date" + }, + "publication_date_before": { + "filter_class": "DateFilter", + "lookup": "lte", + "type": "date" + }, + "rin": { + "filter_class": "JsonbContainsFilter", + "type": "string" + }, + "search": { + "filter_class": "FederalRegisterSearchFilter", + "type": "string" + }, + "significant": { + "filter_class": "BooleanFilter", + "type": "boolean" + }, + "type": { + "filter_class": "DocumentTypeFilter", + "type": "string" + } + }, + "ordering_aliases": [], + "ordering_fields": [ + "comments_close_on", + "document_number", + "effective_on", + "publication_date", + "rank" + ], + "pagination": { + "class": "CachedPageNumberPagination", + "max_page_size": 100 + }, + "shape": { + "expands": {}, + "fields": [ + "abstract", + "action", + "agencies", + "body_html_url", + "cfr_references", + "citation", + "comment_url", + "comments_close_on", + "correction_of", + "corrections", + "dates", + "disposition_notes", + "docket_ids", + "dockets", + "document_number", + "effective_on", + "end_page", + "executive_order_notes", + "executive_order_number", + "html_url", + "not_received_for_publication", + "page_length", + "pdf_url", + "presidential_document_number", + "proclamation_number", + "public_inspection_pdf_url", + "publication_date", + "raw_text_url", + "regulation_id_number_info", + "regulation_id_numbers", + "regulations_dot_gov_info", + "regulations_dot_gov_url", + "significant", + "signing_date", + "start_page", + "subtype", + "title", + "toc_doc", + "toc_subject", + "topics", + "type", + "uuid", + "volume" + ] + }, + "shape_flat_paths": [ + "abstract", + "action", + "agencies", + "body_html_url", + "cfr_references", + "citation", + "comment_url", + "comments_close_on", + "correction_of", + "corrections", + "dates", + "disposition_notes", + "docket_ids", + "dockets", + "document_number", + "effective_on", + "end_page", + "executive_order_notes", + "executive_order_number", + "html_url", + "not_received_for_publication", + "page_length", + "pdf_url", + "presidential_document_number", + "proclamation_number", + "public_inspection_pdf_url", + "publication_date", + "raw_text_url", + "regulation_id_number_info", + "regulation_id_numbers", + "regulations_dot_gov_info", + "regulations_dot_gov_url", + "significant", + "signing_date", + "start_page", + "subtype", + "title", + "toc_doc", + "toc_subject", + "topics", + "type", + "uuid", + "volume" + ], + "shape_gated_by_level": {}, + "shape_supported": true, + "shape_tier_required": null, + "viewset": "federal_register.views.FederalRegisterDocumentViewSet" + }, + "swagger_has_key": true, + "swagger_params": [ + "flat", + "flat_lists", + "joiner", + "limit", + "ordering", + "page", + "shape", + "show_shapes" + ] + }, "forecasts": { "basename": "forecast", "docs_file": null, @@ -10472,6 +10794,7 @@ "resource_key": "itdashboard", "runtime": { "filter_params": [ + "agency", "agency_code", "agency_name", "cio_rating", @@ -10484,6 +10807,10 @@ "updated_time_before" ], "filter_params_detail": { + "agency": { + "filter_class": "OrganizationScopeAgencyFilter", + "type": "string" + }, "agency_code": { "filter_class": "NumberFilter", "type": "number" @@ -11169,9 +11496,15 @@ "archive", "attachment_count", "attachments", + "award_amount", + "award_date", "award_number", + "awardee", + "awardee_uei", "description", + "file_count", "last_updated", + "link_count", "meta", "naics_code", "notice_id", @@ -11211,9 +11544,15 @@ "attachments.resource_id", "attachments.type", "attachments.url", + "award_amount", + "award_date", "award_number", + "awardee", + "awardee_uei", "description", + "file_count", "last_updated", + "link_count", "meta", "meta.link", "meta.notice_type", @@ -11401,6 +11740,8 @@ "filter_params": [ "active", "agency", + "awarded", + "awardee_uei", "first_notice_date_after", "first_notice_date_before", "last_notice_date_after", @@ -11425,6 +11766,14 @@ "filter_class": "PerformantAgencyFilter", "type": "string" }, + "awarded": { + "filter_class": "BooleanFilter", + "type": "boolean" + }, + "awardee_uei": { + "filter_class": "AwardeeUeiFilter", + "type": "string" + }, "first_notice_date_after": { "filter_class": "DateFromToRangeFilter", "lookup": "gte", @@ -11526,6 +11875,18 @@ "url" ] }, + "awards": { + "expands": {}, + "fields": [ + "award_amount", + "award_date", + "award_number", + "awardee", + "awardee_uei", + "notice_id", + "opportunity_id" + ] + }, "department": { "expands": {}, "fields": [ @@ -11557,6 +11918,8 @@ }, "fields": [ "attachments_count", + "files_count", + "links_count", "notice_type", "notices_count" ] @@ -11632,7 +11995,13 @@ "agency_id", "archive_date", "attachments", + "award_amount", + "award_count", + "award_date", "award_number", + "awarded", + "awardee", + "awardee_uei", "department", "department_id", "description", @@ -11654,6 +12023,7 @@ "set_aside", "snippet", "solicitation_number", + "solicitation_opportunity_id", "title" ] }, @@ -11677,7 +12047,21 @@ "attachments.resource_id", "attachments.type", "attachments.url", + "award_amount", + "award_count", + "award_date", "award_number", + "awarded", + "awardee", + "awardee_uei", + "awards", + "awards.award_amount", + "awards.award_date", + "awards.award_number", + "awards.awardee", + "awards.awardee_uei", + "awards.notice_id", + "awards.opportunity_id", "department", "department.abbreviation", "department.cgac", @@ -11696,6 +12080,8 @@ "latest_notice_id", "meta", "meta.attachments_count", + "meta.files_count", + "meta.links_count", "meta.notice_type", "meta.notice_type.code", "meta.notice_type.type", @@ -11748,6 +12134,7 @@ "set_aside.description", "snippet", "solicitation_number", + "solicitation_opportunity_id", "title" ], "shape_gated_by_level": { @@ -11765,6 +12152,8 @@ "swagger_params": [ "active", "agency", + "awarded", + "awardee_uei", "first_notice_date_after", "first_notice_date_before", "flat", @@ -13598,6 +13987,18 @@ "s3_key" ] }, + "organization": { + "expands": {}, + "fields": [ + "agency_code", + "agency_name", + "department_code", + "department_name", + "office_code", + "office_name", + "organization_id" + ] + }, "topics": { "expands": {}, "fields": [ @@ -13642,6 +14043,14 @@ "documents.n_pages", "documents.s3_key", "end_date", + "organization", + "organization.agency_code", + "organization.agency_name", + "organization.department_code", + "organization.department_name", + "organization.office_code", + "organization.office_name", + "organization.organization_id", "out_of_cycle", "program", "sol_download_url", @@ -13681,6 +14090,7 @@ "agency", "close_date_after", "close_date_before", + "cycle_name", "doc_source", "open_date_after", "open_date_before", @@ -13703,8 +14113,7 @@ "type": "choice" }, "agency": { - "filter_class": "CharFilter", - "lookup": "icontains", + "filter_class": "OrganizationScopeAgencyFilter", "type": "string" }, "close_date_after": { @@ -13717,6 +14126,10 @@ "lookup": "lte", "type": "date" }, + "cycle_name": { + "filter_class": "CharFilter", + "type": "string" + }, "doc_source": { "filter_class": "CharFilter", "lookup": "iexact", @@ -13794,6 +14207,18 @@ "title" ] }, + "organization": { + "expands": {}, + "fields": [ + "agency_code", + "agency_name", + "department_code", + "department_name", + "office_code", + "office_name", + "organization_id" + ] + }, "solicitation": { "expands": {}, "fields": [ @@ -13814,6 +14239,7 @@ "activity", "agency", "close_date", + "cycle_name", "description", "doc_source", "due_dates_text", @@ -13836,6 +14262,7 @@ "activity", "agency", "close_date", + "cycle_name", "description", "doc_source", "due_dates_text", @@ -13852,6 +14279,14 @@ "opportunity.response_deadline", "opportunity.solicitation_number", "opportunity.title", + "organization", + "organization.agency_code", + "organization.agency_name", + "organization.department_code", + "organization.department_name", + "organization.office_code", + "organization.office_name", + "organization.organization_id", "release_date", "solicitation", "solicitation.cycle_name", @@ -15485,6 +15920,18 @@ "url" ] }, + "awards": { + "expands": {}, + "fields": [ + "award_amount", + "award_date", + "award_number", + "awardee", + "awardee_uei", + "notice_id", + "opportunity_id" + ] + }, "department": { "expands": {}, "fields": [ @@ -15516,6 +15963,8 @@ }, "fields": [ "attachments_count", + "files_count", + "links_count", "notice_type", "notices_count" ] @@ -15591,7 +16040,13 @@ "agency_id", "archive_date", "attachments", + "award_amount", + "award_count", + "award_date", "award_number", + "awarded", + "awardee", + "awardee_uei", "department", "department_id", "description", @@ -15613,6 +16068,7 @@ "set_aside", "snippet", "solicitation_number", + "solicitation_opportunity_id", "title" ] }, @@ -15638,6 +16094,7 @@ "description", "descriptions", "fiscal_year", + "holder_count", "idv_count", "is_synthetic_solicitation", "last_date_to_order", @@ -15646,6 +16103,7 @@ "name", "opportunity_id", "order_count", + "order_winner_count", "organization", "organization_id", "program_acronym", @@ -16051,6 +16509,7 @@ "description", "descriptions", "fiscal_year", + "holder_count", "idv_count", "is_synthetic_solicitation", "last_date_to_order", @@ -16088,7 +16547,21 @@ "opportunity.attachments.resource_id", "opportunity.attachments.type", "opportunity.attachments.url", + "opportunity.award_amount", + "opportunity.award_count", + "opportunity.award_date", "opportunity.award_number", + "opportunity.awarded", + "opportunity.awardee", + "opportunity.awardee_uei", + "opportunity.awards", + "opportunity.awards.award_amount", + "opportunity.awards.award_date", + "opportunity.awards.award_number", + "opportunity.awards.awardee", + "opportunity.awards.awardee_uei", + "opportunity.awards.notice_id", + "opportunity.awards.opportunity_id", "opportunity.department", "opportunity.department.abbreviation", "opportunity.department.cgac", @@ -16107,6 +16580,8 @@ "opportunity.latest_notice_id", "opportunity.meta", "opportunity.meta.attachments_count", + "opportunity.meta.files_count", + "opportunity.meta.links_count", "opportunity.meta.notice_type", "opportunity.meta.notice_type.code", "opportunity.meta.notice_type.type", @@ -16159,9 +16634,11 @@ "opportunity.set_aside.description", "opportunity.snippet", "opportunity.solicitation_number", + "opportunity.solicitation_opportunity_id", "opportunity.title", "opportunity_id", "order_count", + "order_winner_count", "organization", "organization.agency_code", "organization.agency_name", @@ -16993,6 +17470,7 @@ "entities_subawards", "events", "exclusions", + "federal_register", "forecasts", "grants", "gsa_elibrary_contracts", diff --git a/contracts/observed_shape_types.json b/contracts/observed_shape_types.json index aa4d965..a69ffff 100644 --- a/contracts/observed_shape_types.json +++ b/contracts/observed_shape_types.json @@ -124,6 +124,12 @@ }, "budget/accounts": { "paths": { + "account_category": { + "is_list": false, + "is_optional": true, + "kind": "scalar", + "type": "str" + }, "account_narrative_excerpt": { "is_list": false, "is_optional": true, @@ -302,6 +308,12 @@ "kind": "scalar", "type": "int" }, + "data_through_period": { + "is_list": false, + "is_optional": true, + "kind": "scalar", + "type": "int" + }, "narratives": { "is_list": true, "is_optional": false, @@ -438,6 +450,12 @@ "is_optional": true, "kind": "scalar", "type": "int" + }, + "source_anomalies": { + "is_list": true, + "is_optional": true, + "kind": "scalar", + "type": "dict" } }, "records_seen": 80 @@ -8348,12 +8366,36 @@ "kind": "scalar", "type": "str" }, + "award_amount": { + "is_list": false, + "is_optional": true, + "kind": "scalar", + "type": "str" + }, + "award_date": { + "is_list": false, + "is_optional": true, + "kind": "scalar", + "type": "date" + }, "award_number": { "is_list": false, "is_optional": true, "kind": "scalar", "type": "str" }, + "awardee": { + "is_list": false, + "is_optional": true, + "kind": "scalar", + "type": "str" + }, + "awardee_uei": { + "is_list": false, + "is_optional": true, + "kind": "scalar", + "type": "str" + }, "description": { "is_list": false, "is_optional": true, @@ -8831,12 +8873,95 @@ "kind": "scalar", "type": "str" }, + "award_amount": { + "is_list": false, + "is_optional": true, + "kind": "scalar", + "type": "str" + }, + "award_count": { + "is_list": false, + "is_optional": true, + "kind": "scalar", + "type": "int" + }, + "award_date": { + "is_list": false, + "is_optional": true, + "kind": "scalar", + "type": "date" + }, "award_number": { "is_list": false, "is_optional": true, "kind": "scalar", "type": "str" }, + "awarded": { + "is_list": false, + "is_optional": true, + "kind": "scalar", + "type": "bool" + }, + "awardee": { + "is_list": false, + "is_optional": true, + "kind": "scalar", + "type": "str" + }, + "awardee_uei": { + "is_list": false, + "is_optional": true, + "kind": "scalar", + "type": "str" + }, + "awards": { + "is_list": true, + "is_optional": true, + "kind": "object" + }, + "awards.award_amount": { + "is_list": false, + "is_optional": true, + "kind": "scalar", + "type": "str" + }, + "awards.award_date": { + "is_list": false, + "is_optional": true, + "kind": "scalar", + "type": "date" + }, + "awards.award_number": { + "is_list": false, + "is_optional": true, + "kind": "scalar", + "type": "str" + }, + "awards.awardee": { + "is_list": false, + "is_optional": true, + "kind": "scalar", + "type": "str" + }, + "awards.awardee_uei": { + "is_list": false, + "is_optional": true, + "kind": "scalar", + "type": "str" + }, + "awards.notice_id": { + "is_list": false, + "is_optional": true, + "kind": "scalar", + "type": "str" + }, + "awards.opportunity_id": { + "is_list": false, + "is_optional": true, + "kind": "scalar", + "type": "str" + }, "department": { "is_list": false, "is_optional": false, @@ -9227,6 +9352,12 @@ "kind": "scalar", "type": "str" }, + "solicitation_opportunity_id": { + "is_list": false, + "is_optional": true, + "kind": "scalar", + "type": "str" + }, "title": { "is_list": false, "is_optional": true, @@ -11278,311 +11409,893 @@ }, "records_seen": 160 }, - "subawards": { + "sled/forecasts": { "paths": { - "award_key": { + "agency": { "is_list": false, "is_optional": true, "kind": "scalar", "type": "str" }, - "awarding_office": { + "contact": { "is_list": false, "is_optional": false, "kind": "object" }, - "awarding_office.agency_code": { + "contact.email": { "is_list": false, "is_optional": true, "kind": "scalar", - "type": "int" + "type": "str" }, - "awarding_office.agency_name": { + "contact.name": { "is_list": false, "is_optional": true, "kind": "scalar", "type": "str" }, - "awarding_office.department_code": { + "contact.phone": { "is_list": false, "is_optional": true, "kind": "scalar", - "type": "int" + "type": "str" }, - "awarding_office.department_name": { + "contract_number": { "is_list": false, "is_optional": true, "kind": "scalar", "type": "str" }, - "awarding_office.office_code": { + "contract_term": { "is_list": false, "is_optional": true, "kind": "scalar", - "type": "int" + "type": "str" }, - "awarding_office.office_name": { + "delivery_location": { "is_list": false, "is_optional": true, "kind": "scalar", "type": "str" }, - "fsrs_details": { - "is_list": false, - "is_optional": false, - "kind": "object" - }, - "fsrs_details.id": { + "description": { "is_list": false, "is_optional": true, "kind": "scalar", "type": "str" }, - "fsrs_details.last_modified_date": { + "estimated_advertisement_date": { "is_list": false, "is_optional": true, "kind": "scalar", "type": "datetime" }, - "fsrs_details.month": { - "is_list": false, - "is_optional": true, - "kind": "scalar", - "type": "int" - }, - "fsrs_details.year": { + "estimated_advertisement_raw": { "is_list": false, "is_optional": true, "kind": "scalar", - "type": "int" + "type": "str" }, - "funding_office": { + "estimated_value": { "is_list": false, "is_optional": false, "kind": "object" }, - "funding_office.agency_code": { + "estimated_value.max": { "is_list": false, "is_optional": true, "kind": "scalar", - "type": "int" + "type": "Decimal" }, - "funding_office.agency_name": { + "estimated_value.min": { "is_list": false, "is_optional": true, "kind": "scalar", - "type": "str" + "type": "Decimal" }, - "funding_office.department_code": { + "estimated_value.raw": { "is_list": false, "is_optional": true, "kind": "scalar", - "type": "int" + "type": "str" }, - "funding_office.department_name": { + "first_seen_at": { "is_list": false, "is_optional": true, "kind": "scalar", - "type": "str" + "type": "datetime" }, - "funding_office.office_code": { + "forecast_id": { "is_list": false, "is_optional": true, "kind": "scalar", - "type": "int" + "type": "str" }, - "funding_office.office_name": { + "has_documents": { "is_list": false, "is_optional": true, "kind": "scalar", - "type": "str" - }, - "highly_compensated_officers": { - "is_list": true, - "is_optional": false, - "kind": "object" + "type": "bool" }, - "highly_compensated_officers.amount": { + "incumbent_name": { "is_list": false, "is_optional": true, "kind": "scalar", "type": "str" }, - "highly_compensated_officers.name": { + "last_seen_at": { "is_list": false, "is_optional": true, "kind": "scalar", - "type": "str" + "type": "datetime" }, - "key": { + "mbe_dbe_goal": { "is_list": false, "is_optional": true, "kind": "scalar", - "type": "int" + "type": "str" }, - "piid": { + "modified": { "is_list": false, "is_optional": true, "kind": "scalar", - "type": "str" + "type": "datetime" }, - "place_of_performance": { + "organization": { "is_list": false, "is_optional": false, "kind": "object" }, - "place_of_performance.city": { + "organization.agency": { "is_list": false, "is_optional": true, "kind": "scalar", "type": "str" }, - "place_of_performance.country_code": { + "organization.level": { "is_list": false, "is_optional": true, "kind": "scalar", "type": "str" }, - "place_of_performance.state": { + "organization.state": { "is_list": false, "is_optional": true, "kind": "scalar", "type": "str" }, - "place_of_performance.zip": { - "is_list": false, - "is_optional": true, - "kind": "scalar", - "type": "int" - }, - "prime_awardee_name": { + "procurement_category": { "is_list": false, "is_optional": true, "kind": "scalar", "type": "str" }, - "prime_awardee_uei": { + "procurement_method": { "is_list": false, "is_optional": true, "kind": "scalar", "type": "str" }, - "prime_recipient": { + "raw": { "is_list": false, "is_optional": false, "kind": "object" }, - "prime_recipient.display_name": { + "source_status": { "is_list": false, "is_optional": true, "kind": "scalar", "type": "str" }, - "prime_recipient.uei": { + "source_url": { "is_list": false, "is_optional": true, "kind": "scalar", "type": "str" }, - "recipient_business_types": { + "state": { "is_list": false, "is_optional": true, "kind": "scalar", "type": "str" }, - "recipient_dba_name": { + "title": { + "is_list": false, + "is_optional": true, + "kind": "scalar", + "type": "str" + } + }, + "records_seen": 200 + }, + "sled/opportunities": { + "paths": { + "agency": { "is_list": false, "is_optional": true, "kind": "scalar", "type": "str" }, - "recipient_duns": { + "attachments": { + "is_list": true, + "is_optional": false, + "kind": "object" + }, + "attachments.char_count": { "is_list": false, "is_optional": true, "kind": "scalar", "type": "int" }, - "recipient_name": { + "attachments.checksum": { "is_list": false, "is_optional": true, "kind": "scalar", "type": "str" }, - "recipient_parent_duns": { + "attachments.download_status": { "is_list": false, "is_optional": true, "kind": "scalar", - "type": "int" + "type": "str" }, - "recipient_parent_name": { + "attachments.extension": { "is_list": false, "is_optional": true, "kind": "scalar", "type": "str" }, - "recipient_parent_uei": { + "attachments.extracted_text": { "is_list": false, "is_optional": true, "kind": "scalar", "type": "str" }, - "recipient_uei": { + "attachments.extraction_status": { "is_list": false, "is_optional": true, "kind": "scalar", "type": "str" }, - "subaward_details": { + "attachments.first_seen_at": { "is_list": false, - "is_optional": false, - "kind": "object" + "is_optional": true, + "kind": "scalar", + "type": "datetime" }, - "subaward_details.action_date": { + "attachments.is_contested": { "is_list": false, "is_optional": true, "kind": "scalar", - "type": "datetime" + "type": "bool" }, - "subaward_details.amount": { + "attachments.is_generated_summary": { "is_list": false, "is_optional": true, "kind": "scalar", - "type": "Decimal" + "type": "bool" }, - "subaward_details.description": { + "attachments.mime_type": { "is_list": false, "is_optional": true, "kind": "scalar", "type": "str" }, - "subaward_details.fiscal_year": { + "attachments.name": { "is_list": false, "is_optional": true, "kind": "scalar", - "type": "int" + "type": "str" }, - "subaward_details.number": { + "attachments.pages": { "is_list": false, "is_optional": true, "kind": "scalar", "type": "int" }, - "subaward_details.type": { + "attachments.size_bytes": { "is_list": false, "is_optional": true, "kind": "scalar", - "type": "str" + "type": "int" }, - "subaward_recipient": { + "attachments.word_count": { "is_list": false, - "is_optional": false, - "kind": "object" + "is_optional": true, + "kind": "scalar", + "type": "int" }, - "subaward_recipient.display_name": { + "bid_opening_date": { "is_list": false, "is_optional": true, "kind": "scalar", - "type": "str" + "type": "datetime" + }, + "bid_opening_raw": { + "is_list": false, + "is_optional": true, + "kind": "scalar", + "type": "str" + }, + "category_codes": { + "is_list": true, + "is_optional": true, + "kind": "scalar", + "type": "dict" + }, + "contact": { + "is_list": false, + "is_optional": false, + "kind": "object" + }, + "contact.email": { + "is_list": false, + "is_optional": true, + "kind": "scalar", + "type": "str" + }, + "contact.name": { + "is_list": false, + "is_optional": true, + "kind": "scalar", + "type": "str" + }, + "contact.phone": { + "is_list": false, + "is_optional": true, + "kind": "scalar", + "type": "str" + }, + "delisted_at": { + "is_list": false, + "is_optional": true, + "kind": "scalar", + "type": "datetime" + }, + "description": { + "is_list": false, + "is_optional": true, + "kind": "scalar", + "type": "str" + }, + "first_seen_at": { + "is_list": false, + "is_optional": true, + "kind": "scalar", + "type": "datetime" + }, + "has_documents": { + "is_list": false, + "is_optional": true, + "kind": "scalar", + "type": "bool" + }, + "jurisdiction": { + "is_list": false, + "is_optional": true, + "kind": "scalar", + "type": "str" + }, + "last_change_seen_at": { + "is_list": false, + "is_optional": true, + "kind": "scalar", + "type": "datetime" + }, + "last_seen_at": { + "is_list": false, + "is_optional": true, + "kind": "scalar", + "type": "datetime" + }, + "meta": { + "is_list": false, + "is_optional": false, + "kind": "object" + }, + "meta.attachment_count": { + "is_list": false, + "is_optional": true, + "kind": "scalar", + "type": "int" + }, + "meta.jurisdiction_declared": { + "is_list": false, + "is_optional": true, + "kind": "scalar", + "type": "bool" + }, + "meta.last_change_source_declared": { + "is_list": false, + "is_optional": true, + "kind": "scalar", + "type": "bool" + }, + "meta.last_revision_kind": { + "is_list": false, + "is_optional": true, + "kind": "scalar", + "type": "str" + }, + "meta.revision_count": { + "is_list": false, + "is_optional": true, + "kind": "scalar", + "type": "int" + }, + "modified": { + "is_list": false, + "is_optional": true, + "kind": "scalar", + "type": "datetime" + }, + "opportunity_id": { + "is_list": false, + "is_optional": true, + "kind": "scalar", + "type": "str" + }, + "organization": { + "is_list": false, + "is_optional": false, + "kind": "object" + }, + "organization.agency": { + "is_list": false, + "is_optional": true, + "kind": "scalar", + "type": "str" + }, + "organization.level": { + "is_list": false, + "is_optional": true, + "kind": "scalar", + "type": "str" + }, + "organization.state": { + "is_list": false, + "is_optional": true, + "kind": "scalar", + "type": "str" + }, + "posted_date": { + "is_list": false, + "is_optional": true, + "kind": "scalar", + "type": "datetime" + }, + "raw": { + "is_list": false, + "is_optional": false, + "kind": "object" + }, + "response_deadline": { + "is_list": false, + "is_optional": true, + "kind": "scalar", + "type": "datetime" + }, + "response_deadline_original": { + "is_list": false, + "is_optional": true, + "kind": "scalar", + "type": "datetime" + }, + "revisions": { + "is_list": true, + "is_optional": false, + "kind": "object" + }, + "revisions.changed_fields": { + "is_list": true, + "is_optional": true, + "kind": "scalar", + "type": "str" + }, + "revisions.changes": { + "is_list": false, + "is_optional": true, + "kind": "scalar", + "type": "dict" + }, + "revisions.kind": { + "is_list": false, + "is_optional": true, + "kind": "scalar", + "type": "str" + }, + "revisions.observed_at": { + "is_list": false, + "is_optional": true, + "kind": "scalar", + "type": "datetime" + }, + "revisions.sequence": { + "is_list": false, + "is_optional": true, + "kind": "scalar", + "type": "int" + }, + "revisions.source_declared": { + "is_list": false, + "is_optional": true, + "kind": "scalar", + "type": "bool" + }, + "snippet": { + "is_list": false, + "is_optional": true, + "kind": "scalar", + "type": "str" + }, + "solicitation_number": { + "is_list": false, + "is_optional": true, + "kind": "scalar", + "type": "str" + }, + "solicitation_type": { + "is_list": false, + "is_optional": true, + "kind": "scalar", + "type": "str" + }, + "solicitation_type_source": { + "is_list": false, + "is_optional": true, + "kind": "scalar", + "type": "str" + }, + "source_status": { + "is_list": false, + "is_optional": true, + "kind": "scalar", + "type": "str" + }, + "source_url": { + "is_list": false, + "is_optional": true, + "kind": "scalar", + "type": "str" + }, + "state": { + "is_list": false, + "is_optional": true, + "kind": "scalar", + "type": "str" + }, + "status": { + "is_list": false, + "is_optional": true, + "kind": "scalar", + "type": "str" + }, + "status_computed_at": { + "is_list": false, + "is_optional": true, + "kind": "scalar", + "type": "datetime" + }, + "status_reason": { + "is_list": false, + "is_optional": true, + "kind": "scalar", + "type": "str" + }, + "title": { + "is_list": false, + "is_optional": true, + "kind": "scalar", + "type": "str" + } + }, + "records_seen": 280 + }, + "subawards": { + "paths": { + "award_key": { + "is_list": false, + "is_optional": true, + "kind": "scalar", + "type": "str" + }, + "awarding_office": { + "is_list": false, + "is_optional": false, + "kind": "object" + }, + "awarding_office.agency_code": { + "is_list": false, + "is_optional": true, + "kind": "scalar", + "type": "int" + }, + "awarding_office.agency_name": { + "is_list": false, + "is_optional": true, + "kind": "scalar", + "type": "str" + }, + "awarding_office.department_code": { + "is_list": false, + "is_optional": true, + "kind": "scalar", + "type": "int" + }, + "awarding_office.department_name": { + "is_list": false, + "is_optional": true, + "kind": "scalar", + "type": "str" + }, + "awarding_office.office_code": { + "is_list": false, + "is_optional": true, + "kind": "scalar", + "type": "int" + }, + "awarding_office.office_name": { + "is_list": false, + "is_optional": true, + "kind": "scalar", + "type": "str" + }, + "fsrs_details": { + "is_list": false, + "is_optional": false, + "kind": "object" + }, + "fsrs_details.id": { + "is_list": false, + "is_optional": true, + "kind": "scalar", + "type": "str" + }, + "fsrs_details.last_modified_date": { + "is_list": false, + "is_optional": true, + "kind": "scalar", + "type": "datetime" + }, + "fsrs_details.month": { + "is_list": false, + "is_optional": true, + "kind": "scalar", + "type": "int" + }, + "fsrs_details.year": { + "is_list": false, + "is_optional": true, + "kind": "scalar", + "type": "int" + }, + "funding_office": { + "is_list": false, + "is_optional": false, + "kind": "object" + }, + "funding_office.agency_code": { + "is_list": false, + "is_optional": true, + "kind": "scalar", + "type": "int" + }, + "funding_office.agency_name": { + "is_list": false, + "is_optional": true, + "kind": "scalar", + "type": "str" + }, + "funding_office.department_code": { + "is_list": false, + "is_optional": true, + "kind": "scalar", + "type": "int" + }, + "funding_office.department_name": { + "is_list": false, + "is_optional": true, + "kind": "scalar", + "type": "str" + }, + "funding_office.office_code": { + "is_list": false, + "is_optional": true, + "kind": "scalar", + "type": "int" + }, + "funding_office.office_name": { + "is_list": false, + "is_optional": true, + "kind": "scalar", + "type": "str" + }, + "highly_compensated_officers": { + "is_list": true, + "is_optional": false, + "kind": "object" + }, + "highly_compensated_officers.amount": { + "is_list": false, + "is_optional": true, + "kind": "scalar", + "type": "str" + }, + "highly_compensated_officers.name": { + "is_list": false, + "is_optional": true, + "kind": "scalar", + "type": "str" + }, + "key": { + "is_list": false, + "is_optional": true, + "kind": "scalar", + "type": "int" + }, + "piid": { + "is_list": false, + "is_optional": true, + "kind": "scalar", + "type": "str" + }, + "place_of_performance": { + "is_list": false, + "is_optional": false, + "kind": "object" + }, + "place_of_performance.city": { + "is_list": false, + "is_optional": true, + "kind": "scalar", + "type": "str" + }, + "place_of_performance.country_code": { + "is_list": false, + "is_optional": true, + "kind": "scalar", + "type": "str" + }, + "place_of_performance.state": { + "is_list": false, + "is_optional": true, + "kind": "scalar", + "type": "str" + }, + "place_of_performance.zip": { + "is_list": false, + "is_optional": true, + "kind": "scalar", + "type": "int" + }, + "prime_awardee_name": { + "is_list": false, + "is_optional": true, + "kind": "scalar", + "type": "str" + }, + "prime_awardee_uei": { + "is_list": false, + "is_optional": true, + "kind": "scalar", + "type": "str" + }, + "prime_recipient": { + "is_list": false, + "is_optional": false, + "kind": "object" + }, + "prime_recipient.display_name": { + "is_list": false, + "is_optional": true, + "kind": "scalar", + "type": "str" + }, + "prime_recipient.uei": { + "is_list": false, + "is_optional": true, + "kind": "scalar", + "type": "str" + }, + "recipient_business_types": { + "is_list": false, + "is_optional": true, + "kind": "scalar", + "type": "str" + }, + "recipient_dba_name": { + "is_list": false, + "is_optional": true, + "kind": "scalar", + "type": "str" + }, + "recipient_duns": { + "is_list": false, + "is_optional": true, + "kind": "scalar", + "type": "int" + }, + "recipient_name": { + "is_list": false, + "is_optional": true, + "kind": "scalar", + "type": "str" + }, + "recipient_parent_duns": { + "is_list": false, + "is_optional": true, + "kind": "scalar", + "type": "int" + }, + "recipient_parent_name": { + "is_list": false, + "is_optional": true, + "kind": "scalar", + "type": "str" + }, + "recipient_parent_uei": { + "is_list": false, + "is_optional": true, + "kind": "scalar", + "type": "str" + }, + "recipient_uei": { + "is_list": false, + "is_optional": true, + "kind": "scalar", + "type": "str" + }, + "subaward_details": { + "is_list": false, + "is_optional": false, + "kind": "object" + }, + "subaward_details.action_date": { + "is_list": false, + "is_optional": true, + "kind": "scalar", + "type": "datetime" + }, + "subaward_details.amount": { + "is_list": false, + "is_optional": true, + "kind": "scalar", + "type": "Decimal" + }, + "subaward_details.description": { + "is_list": false, + "is_optional": true, + "kind": "scalar", + "type": "str" + }, + "subaward_details.fiscal_year": { + "is_list": false, + "is_optional": true, + "kind": "scalar", + "type": "int" + }, + "subaward_details.number": { + "is_list": false, + "is_optional": true, + "kind": "scalar", + "type": "int" + }, + "subaward_details.type": { + "is_list": false, + "is_optional": true, + "kind": "scalar", + "type": "str" + }, + "subaward_recipient": { + "is_list": false, + "is_optional": false, + "kind": "object" + }, + "subaward_recipient.display_name": { + "is_list": false, + "is_optional": true, + "kind": "scalar", + "type": "str" }, "subaward_recipient.uei": { "is_list": false, @@ -11787,6 +12500,12 @@ "kind": "scalar", "type": "int" }, + "holder_count": { + "is_list": false, + "is_optional": true, + "kind": "scalar", + "type": "int" + }, "idv_count": { "is_list": false, "is_optional": true, @@ -11900,6 +12619,95 @@ "kind": "scalar", "type": "str" }, + "opportunity.award_amount": { + "is_list": false, + "is_optional": true, + "kind": "scalar", + "type": "str" + }, + "opportunity.award_count": { + "is_list": false, + "is_optional": true, + "kind": "scalar", + "type": "int" + }, + "opportunity.award_date": { + "is_list": false, + "is_optional": true, + "kind": "scalar", + "type": "date" + }, + "opportunity.awarded": { + "is_list": false, + "is_optional": true, + "kind": "scalar", + "type": "bool" + }, + "opportunity.awardee": { + "is_list": false, + "is_optional": true, + "kind": "scalar", + "type": "str" + }, + "opportunity.awardee_uei": { + "is_list": false, + "is_optional": true, + "kind": "scalar", + "type": "str" + }, + "opportunity.awards": { + "is_list": true, + "is_optional": true, + "kind": "object" + }, + "opportunity.awards.award_amount": { + "is_list": false, + "is_optional": true, + "kind": "scalar", + "type": "str" + }, + "opportunity.awards.award_date": { + "is_list": false, + "is_optional": true, + "kind": "scalar", + "type": "date" + }, + "opportunity.awards.award_number": { + "is_list": false, + "is_optional": true, + "kind": "scalar", + "type": "str" + }, + "opportunity.awards.awardee": { + "is_list": false, + "is_optional": true, + "kind": "scalar", + "type": "str" + }, + "opportunity.awards.awardee_uei": { + "is_list": false, + "is_optional": true, + "kind": "scalar", + "type": "str" + }, + "opportunity.awards.notice_id": { + "is_list": false, + "is_optional": true, + "kind": "scalar", + "type": "str" + }, + "opportunity.awards.opportunity_id": { + "is_list": false, + "is_optional": true, + "kind": "scalar", + "type": "str" + }, + "opportunity.solicitation_opportunity_id": { + "is_list": false, + "is_optional": true, + "kind": "scalar", + "type": "str" + }, "opportunity_id": { "is_list": false, "is_optional": true, @@ -11912,6 +12720,12 @@ "kind": "scalar", "type": "int" }, + "order_winner_count": { + "is_list": false, + "is_optional": true, + "kind": "scalar", + "type": "int" + }, "organization": { "is_list": false, "is_optional": false, diff --git a/docs/API_REFERENCE.md b/docs/API_REFERENCE.md index fe32342..066e29d 100644 --- a/docs/API_REFERENCE.md +++ b/docs/API_REFERENCE.md @@ -243,12 +243,31 @@ Forecast search, with optional shaping. Search SAM.gov opportunities with shaping. `opportunity_id` is a typed filter for fetching specific opportunities through the list endpoint. +`awarded` (boolean) filters by whether the opportunity has an award, either posted on it or linked to it from a separate award notice, and `awardee_uei` filters by the awardee's UEI (case-insensitive; OR several with `|`). +Both search every opportunity rather than only active ones, and both require Tango API 5.5.0. + +**Attachment counts.** `meta(attachments_count,files_count,links_count)` counts an opportunity's attachments. +An attachment's `type` is `file` (a document; only a file has extracted text) or `link` (a URL the notice lists). +`files_count` and `links_count` count each, and `attachments_count` still counts every attachment, links included. +An attachment of any other type counts only in the total, so `files_count + links_count` need not equal `attachments_count`. +Both new counts are `null` until the opportunity has been counted, so read `null` as unknown, not zero. +They require Tango API 5.9.0 and are not in the SDK's default shape; name them in `shape`. + +```ts +const page = await client.listOpportunities({ + shape: "opportunity_id,title,meta(attachments_count,files_count,links_count)", +}); +``` + --- ## Notices ### `listNotices(options)` +**Attachment counts.** `file_count` and `link_count` sit beside `attachment_count` and follow the same rules as the opportunity counts above: files are documents, links are URLs the notice lists, `attachment_count` still counts every attachment, the two need not add up to it, and each is `null` (unknown, not zero) until the notice has been counted. +They require Tango API 5.9.0; name them in `shape`, e.g. `shape: "notice_id,title,attachment_count,file_count,link_count"`. + --- ## Grants @@ -450,6 +469,67 @@ Returns a `ContractAppealRecord`. Every property on it is optional, for the same --- +## Federal Register + +Federal Register documents: rules, proposed rules, notices and presidential documents published since 1994. + +A document is identified by `uuid`. `document_number` is not unique on its own: the Federal Register reused some numbers before 2016, so `document_number` can return more than one document. + +### `listFederalRegisterDocuments(options?)` + +```ts +const documents = await client.listFederalRegisterDocuments({ + type: "Proposed Rule", + comments_open: true, + agency: "EPA", + limit: 25, +}); + +for (const doc of documents.results) { + console.log(doc.document_number, doc.publication_date, doc.title, doc.comments_close_on); +} +``` + +#### Parameters (Federal Register) + +| Name | Type | Description | +| ------------------------------------------ | ------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `search` | `string` | Ranked full-text search over the title, abstract and action. Wrap in double quotes for a phrase. | +| `document_number` | `string` | Exact FR document number, e.g. `2016-31922`. OR several with `\|`. | +| `type` | `string` | `Notice`, `Rule`, `Proposed Rule`, `Presidential Document`, `Correction`, `Sunshine Act Document` or `Uncategorized Document` (case-insensitive). OR several with `\|`. An unknown type is an error, not an empty page. | +| `agency` | `string` | A Tango agency name, abbreviation, code or organization key, e.g. `EPA`. Matches a document when any agency it lists falls within that organization, so a department includes its sub-agencies. OR several with `\|`. | +| `fr_agency` | `string` | The Federal Register's own agency slug, e.g. `environmental-protection-agency`. OR several with `\|`. | +| `publication_date_after` / `_before` | `string` | Publication date range (`YYYY-MM-DD`). | +| `effective_on_after` / `_before` | `string` | Effective date range (`YYYY-MM-DD`). | +| `comments_close_on_after` / `_before` | `string` | Comment-deadline range (`YYYY-MM-DD`). | +| `comments_open` | `boolean` | `true` for documents whose comment period closes today or later, `false` for those already closed. Documents with no comment deadline match neither. | +| `cfr_title` | `string \| number` | A CFR title number, e.g. `40`. | +| `cfr_part` | `string \| number` | A CFR part number, e.g. `52`. Requires `cfr_title`, and both must match the same CFR reference, so `cfr_title: 40, cfr_part: 52` finds 40 CFR 52. | +| `significant` | `boolean` | Significant under Executive Order 12866. | +| `rin` | `string` | A Regulation Identifier Number, e.g. `2060-AV16`. OR several with `\|`. | +| `executive_order_number` | `string \| number` | Exact executive order number. | +| `ordering` | `string` | `publication_date` (default `-publication_date`), `effective_on`, `comments_close_on`, `document_number` or `rank`. | + +`rank` ordering requires a non-empty `search`. The standard `page` / `limit` / `shape` / `flat` / `flatLists` / `joiner` options apply, and the list defaults to `ShapeConfig.FEDERAL_REGISTER_MINIMAL`. + +Documents published before 2008 are mostly `Uncategorized Document`, so a `type` filter undercounts that era. + +### `getFederalRegisterDocument(uuid, options?)` + +```ts +const document = await client.getFederalRegisterDocument("00000000-0000-0000-0000-000000000001", { + shape: "uuid,document_number,title,full_text", +}); +``` + +Takes the document's `uuid`, not its `document_number`. To look one up by number, use `listFederalRegisterDocuments({ document_number })`. Defaults to `ShapeConfig.FEDERAL_REGISTER_COMPREHENSIVE`. + +- `agencies`, `cfr_references`, `dockets` and `topics` are the Federal Register's own structures, served as published. `agencies` carries Federal Register agency slugs, not Tango organization keys. +- `full_text`, the document's plain text, is available on this method only and only when named in `shape`. It can run to several MB, so neither default shape includes it. +- `publication_date`, `effective_on`, `comments_close_on` and `signing_date` are parsed to `Date`. The `FederalRegisterDocument` interface in `@makegov/tango-node/models` lists every field. + +--- + ## IT Dashboard ### `listItDashboard(options?)` @@ -466,6 +546,8 @@ const investment = await client.getItDashboard("023-000001234"); `listItDashboard` also accepts `previous_uii` as a typed filter, for tracing an investment across UII renumbering. +`agency` takes a Tango agency name, abbreviation, code or organization key (e.g. `EPA`) and matches the whole organization, so a department includes its sub-agencies; OR several with `|`. It is available at every plan and requires Tango API 5.3.0. + --- ## Budget Accounts @@ -662,10 +744,13 @@ const exclusions = await client.listExclusions({ }); ``` -Typed filters: `uei`, `entity_uei`, `cage_code`, `npi`, `classification_type`, `exclusion_type`, `exclusion_program`, `excluding_agency_code`, `excluding_agency_name`, `active`, `delisted`, `activate_date_after` / `activate_date_before`, `termination_date_after` / `termination_date_before`, `update_date_after` / `update_date_before`, `search`, `ordering`. +Typed filters: `uei`, `entity_uei`, `cage_code`, `npi`, `classification_type`, `exclusion_type`, `exclusion_program`, `excluding_agency_code`, `excluding_agency_name`, `agency`, `active`, `delisted`, `activate_date_after` / `activate_date_before`, `termination_date_after` / `termination_date_before`, `update_date_after` / `update_date_before`, `search`, `ordering`. `is_currently_excluded` is **derived at query time** — filter with `active: true` for records currently in effect rather than shaping on it. +`agency` scopes by the excluding agency: it takes a Tango agency name, abbreviation, code or organization key and matches the whole organization, so a department includes its sub-agencies (Tango API 5.3.0). +The `organization(...)` expand and `organization_id` are accepted in `shape`. + --- ## SBIR / STTR @@ -682,7 +767,7 @@ const topics = await client.listSbirTopics({ }); ``` -Typed filters: `topic_number`, `solicitation_number`, `agency`, `activity`, `year`, `doc_source`, `open_date_after` / `open_date_before`, `close_date_after` / `close_date_before`, `release_date_after` / `release_date_before`, `search`, `ordering`. +Typed filters: `topic_number`, `solicitation_number`, `agency`, `activity`, `year`, `doc_source`, `cycle_name` (exact DSIP solicitation cycle, e.g. `DOD_SBIR_2026_P1_CBZ`; Tango API 5.3.1), `open_date_after` / `open_date_before`, `close_date_after` / `close_date_before`, `release_date_after` / `release_date_before`, `search`, `ordering`. ### `listSbirSolicitations(options?)` / `getSbirSolicitation(solicitationId, options?)` diff --git a/docs/SHAPES.md b/docs/SHAPES.md index e25df6c..8847a53 100644 --- a/docs/SHAPES.md +++ b/docs/SHAPES.md @@ -101,6 +101,8 @@ import { TangoClient, ShapeConfig } from "@makegov/tango-node"; | `ShapeConfig.EXCLUSIONS_MINIMAL` | `listExclusions()` | | `ShapeConfig.SBIR_TOPICS_MINIMAL` | `listSbirTopics()` | | `ShapeConfig.SBIR_SOLICITATIONS_MINIMAL` | `listSbirSolicitations()` | +| `ShapeConfig.FEDERAL_REGISTER_MINIMAL` | `listFederalRegisterDocuments()` | +| `ShapeConfig.FEDERAL_REGISTER_COMPREHENSIVE` | `getFederalRegisterDocument()` | These are plain strings — you can use them directly or as a starting point: diff --git a/scripts/check-filter-shape-conformance.ts b/scripts/check-filter-shape-conformance.ts index b5bf2a5..4dabb2d 100644 --- a/scripts/check-filter-shape-conformance.ts +++ b/scripts/check-filter-shape-conformance.ts @@ -69,6 +69,7 @@ export const RESOURCE_TO_METHOD: Record = { offices: "listOffices", protests: "listProtests", contract_appeals: "listContractAppeals", + federal_register: "listFederalRegisterDocuments", // eBuy requests are published by the API but not yet ported — baselined as a tracked gap. "ebuy/requests": null, psc: "listPsc", diff --git a/scripts/check-shape-coverage.ts b/scripts/check-shape-coverage.ts index aea38e4..c98b303 100644 --- a/scripts/check-shape-coverage.ts +++ b/scripts/check-shape-coverage.ts @@ -67,6 +67,7 @@ export const RESOURCE_TO_MODEL: Record = { budget_accounts: "BudgetAccount", protests: "Protest", contract_appeals: "ContractAppeal", + federal_register: "FederalRegisterDocument", offices: "Office", assistance_listings: "AssistanceListing", business_types: "BusinessType", diff --git a/src/client.ts b/src/client.ts index 59fad05..796e034 100644 --- a/src/client.ts +++ b/src/client.ts @@ -353,6 +353,10 @@ export interface ListOpportunitiesOptions extends ListOptionsBase { notice_type?: string; /** Filter by opportunity id (the detail-endpoint identifier). */ opportunity_id?: string; + /** Whether the opportunity has an award, either posted on it or linked to it from a separate award notice. Searches every opportunity rather than only active ones. Requires Tango API 5.5.0. */ + awarded?: boolean; + /** The awardee's UEI (case-insensitive). Multi-value OR via `|`. Searches every opportunity rather than only active ones. Requires Tango API 5.5.0. */ + awardee_uei?: string; ordering?: string; place_of_performance?: string; psc?: string; @@ -427,6 +431,12 @@ export interface ListBudgetAccountsOptions extends ListOptionsBase { fiscal_year_gte?: number | string; /** Legacy alias remapped to `fiscal_year__lte`. */ fiscal_year_lte?: number | string; + /** File A fiscal period (1-12) the account-year's figures run through; below 12 the fiscal year is partial. */ + data_through_period?: number | string; + data_through_period__gte?: number | string; + data_through_period__lte?: number | string; + /** `true` for account-years with no File A data (`data_through_period` is null), `false` for those with it. */ + data_through_period__isnull?: boolean; agency_code?: string; agency_code__in?: string; bureau_name?: string; @@ -440,6 +450,12 @@ export interface ListBudgetAccountsOptions extends ListOptionsBase { on_off_budget?: string; subfunction_code?: string; subfunction_code__in?: string; + /** Account category, e.g. `"budgetary"` or `"credit_financing"`. */ + account_category?: string; + /** Comma-separated list of account categories. */ + account_category__in?: string; + /** Agency by name, abbreviation, or code; a department rolls up across its bureaus. Multi-value OR via `|`. */ + agency?: string; // President's-budget requested BA requested_ba?: number | string; @@ -629,6 +645,8 @@ export interface ListExclusionsOptions extends ListOptionsBase { exclusion_program?: string; excluding_agency_code?: string; excluding_agency_name?: string; + /** The excluding agency by name, abbreviation, code or organization key (e.g. `EPA`); a department includes its sub-agencies. Multi-value OR via `|`. Requires Tango API 5.3.0. */ + agency?: string; /** True returns only records currently in effect. `is_currently_excluded` is derived at query time, so filter with this rather than shaping on it. */ active?: boolean; delisted?: boolean; @@ -655,6 +673,8 @@ export interface ListSbirTopicsOptions extends ListOptionsBase { activity?: string; year?: number; doc_source?: string; + /** DSIP solicitation cycle, exact (e.g. `DOD_SBIR_2026_P1_CBZ`). Requires Tango API 5.3.1. */ + cycle_name?: string; open_date_after?: string; open_date_before?: string; close_date_after?: string; @@ -988,10 +1008,59 @@ export interface ListContractAppealsOptions extends ListOptionsBase { [key: string]: unknown; } +/** + * Federal Register document list options — matches `tango_python.TangoClient.list_federal_register_documents`. + * + * Rules, proposed rules, notices and presidential documents published in the Federal Register since 1994. + */ +export interface ListFederalRegisterDocumentsOptions extends ListOptionsBase { + /** Separator for flattened keys. Only meaningful alongside `flat`. */ + joiner?: string; + /** Ranked full-text search over the title, abstract and action. Wrap in double quotes for a phrase. */ + search?: string; + /** Exact FR document number, e.g. `2016-31922`. Multi-value OR via `|`. A number the Federal Register reused before 2016 matches every document that carries it. */ + document_number?: string; + /** `Notice`, `Rule`, `Proposed Rule`, `Presidential Document`, `Correction`, `Sunshine Act Document` or `Uncategorized Document` (case-insensitive). Multi-value OR via `|`. An unknown type is an error, not an empty page. Documents published before 2008 are mostly `Uncategorized Document`, so a type filter undercounts that era. */ + type?: string; + /** A Tango agency name, abbreviation, code or organization key, e.g. `EPA`. Matches a document when any agency it lists falls within that organization, so a department includes its sub-agencies. Multi-value OR via `|`. */ + agency?: string; + /** The Federal Register's own agency slug, e.g. `environmental-protection-agency`. Multi-value OR via `|`. */ + fr_agency?: string; + /** Published on or after (YYYY-MM-DD). */ + publication_date_after?: string; + /** Published on or before (YYYY-MM-DD). */ + publication_date_before?: string; + /** Effective on or after (YYYY-MM-DD). */ + effective_on_after?: string; + /** Effective on or before (YYYY-MM-DD). */ + effective_on_before?: string; + /** Comment period closes on or after (YYYY-MM-DD). */ + comments_close_on_after?: string; + /** Comment period closes on or before (YYYY-MM-DD). */ + comments_close_on_before?: string; + /** `true` for documents whose comment period closes today or later, `false` for those already closed. Documents with no comment deadline match neither. */ + comments_open?: boolean; + /** A CFR title number, e.g. `40`. */ + cfr_title?: string | number; + /** A CFR part number, e.g. `52`. Requires `cfr_title`, and both must match the same CFR reference. */ + cfr_part?: string | number; + /** Significant under Executive Order 12866. */ + significant?: boolean; + /** A Regulation Identifier Number, e.g. `2060-AV16`. Multi-value OR via `|`. */ + rin?: string; + /** Exact executive order number. */ + executive_order_number?: string | number; + /** Sort field (publication_date, effective_on, comments_close_on, document_number, rank). Defaults to `-publication_date`; `rank` requires a non-empty `search`. */ + ordering?: string; + [key: string]: unknown; +} + export interface ListItDashboardOptions { page?: number; limit?: number; search?: string; + /** Agency by name, abbreviation, code or organization key (e.g. `EPA`); a department includes its sub-agencies. Multi-value OR via `|`. Available at every plan. Requires Tango API 5.3.0. */ + agency?: string; agency_code?: string; agency_name?: string; type_of_investment?: string; @@ -2711,6 +2780,37 @@ export class TangoClient { return await this.http.get(`/api/contract_appeals/${encodeURIComponent(uuid)}/`, params); } + /** + * List Federal Register documents (`/api/federal_register/`). + * + * Rules, proposed rules, notices and presidential documents published in the Federal Register since 1994. + * + * `document_number` is not unique on its own: the Federal Register reused some numbers before 2016, so `document_number` can match more than one document. + */ + async listFederalRegisterDocuments(options: ListFederalRegisterDocumentsOptions = {}): Promise>> { + return this._shapedPaginatedList("/api/federal_register/", "FederalRegisterDocument", ShapeConfig.FEDERAL_REGISTER_MINIMAL, options); + } + + /** + * Get a single Federal Register document by uuid (`/api/federal_register/{uuid}/`). + * + * The route takes the document's `uuid`, not its `document_number`, which is not unique on its own. To look a document up by number, use `listFederalRegisterDocuments({ document_number })`. + * + * Name `full_text` in `shape` to get the document's plain text. It can run to several MB, so the default shape leaves it out. + */ + async getFederalRegisterDocument( + uuid: string, + options: { shape?: string | null; flat?: boolean; flatLists?: boolean; joiner?: string } = {}, + ): Promise> { + if (!uuid) throw new TangoValidationError("Federal Register document uuid is required"); + return this._shapedGet( + `/api/federal_register/${encodeURIComponent(uuid)}/`, + "FederalRegisterDocument", + ShapeConfig.FEDERAL_REGISTER_COMPREHENSIVE, + options, + ); + } + /** List IT Dashboard investments. */ async listItDashboard(options: ListItDashboardOptions = {}): Promise> { return this._genericPaginatedList("/api/itdashboard/", options); diff --git a/src/config.ts b/src/config.ts index 04936e6..8b81b70 100644 --- a/src/config.ts +++ b/src/config.ts @@ -27,6 +27,22 @@ export const ShapeConfig = { // Default for listProtests() PROTESTS_MINIMAL: "case_id,case_number,title,source_system,outcome,filed_date", + // Default for listFederalRegisterDocuments(). Mirrors the API's own list default. + FEDERAL_REGISTER_MINIMAL: + "uuid,document_number,publication_date,type,subtype,title,abstract,action," + + "agencies,cfr_references,citation,significant,comments_close_on,effective_on," + + "html_url,pdf_url", + + // Default for getFederalRegisterDocument(). Mirrors the API's own retrieve default. + // `full_text` is left out on purpose: it can run to several MB. + FEDERAL_REGISTER_COMPREHENSIVE: + "uuid,document_number,publication_date,type,subtype,title,abstract,action," + + "agencies,cfr_references,citation,significant,comments_close_on,effective_on," + + "html_url,pdf_url,dates,signing_date,start_page,end_page,volume,docket_ids," + + "dockets,regulation_id_numbers,topics,correction_of,corrections," + + "executive_order_number,presidential_document_number,proclamation_number," + + "comment_url,regulations_dot_gov_url,raw_text_url,body_html_url", + // Default for listGrants() GRANTS_MINIMAL: "grant_id,opportunity_number,title,status(*),agency_code", diff --git a/src/index.ts b/src/index.ts index 3aa20a0..47a472c 100644 --- a/src/index.ts +++ b/src/index.ts @@ -30,6 +30,7 @@ export type { ListSledForecastsOptions, ListProtestsOptions, ListContractAppealsOptions, + ListFederalRegisterDocumentsOptions, ListItDashboardOptions, ListMetricsOptions, ResolveInput, diff --git a/src/models/FederalRegister.ts b/src/models/FederalRegister.ts new file mode 100644 index 0000000..d9082d4 --- /dev/null +++ b/src/models/FederalRegister.ts @@ -0,0 +1,60 @@ +/** + * Federal Register document (`/api/federal_register/`): a rule, proposed rule, notice or presidential document published since 1994. + * + * The endpoint uses shape-on-demand: which fields appear depends on the `?shape=` query param, so EVERY field is optional. + * + * A document is identified by `uuid`. `document_number` is not unique on its own: the Federal Register reused some numbers before 2016. + * + * `agencies`, `cfr_references`, `dockets` and `topics` are the Federal Register's own structures, served as published. `agencies` carries Federal Register agency slugs, not Tango organization keys. + * + * Date fields are typed as the API's ISO strings; a shaped response from the client parses them to `Date`. + */ +export interface FederalRegisterDocument { + uuid?: string; + document_number?: string | null; + publication_date?: string | null; + citation?: string | null; + title?: string | null; + /** `Notice`, `Rule`, `Proposed Rule`, `Presidential Document`, `Correction`, `Sunshine Act Document` or `Uncategorized Document`. Documents published before 2008 are mostly `Uncategorized Document`. */ + type?: string | null; + subtype?: string | null; + abstract?: string | null; + action?: string | null; + dates?: string | null; + comments_close_on?: string | null; + effective_on?: string | null; + signing_date?: string | null; + start_page?: number | null; + end_page?: number | null; + page_length?: number | null; + volume?: number | null; + /** Significant under Executive Order 12866. */ + significant?: boolean | null; + toc_doc?: string | null; + toc_subject?: string | null; + correction_of?: string | null; + corrections?: string[] | null; + disposition_notes?: string | null; + executive_order_notes?: string | null; + executive_order_number?: string | null; + presidential_document_number?: string | null; + proclamation_number?: string | null; + not_received_for_publication?: string | null; + comment_url?: string | null; + regulations_dot_gov_url?: string | null; + html_url?: string | null; + body_html_url?: string | null; + pdf_url?: string | null; + public_inspection_pdf_url?: string | null; + raw_text_url?: string | null; + agencies?: Array> | null; + cfr_references?: Array> | null; + dockets?: Array> | null; + docket_ids?: string[] | null; + topics?: string[] | null; + regulation_id_numbers?: string[] | null; + regulation_id_number_info?: Record | null; + regulations_dot_gov_info?: Record | null; + /** The document's plain text. Served by `getFederalRegisterDocument()` only, and only when named in `shape`; it can run to several MB. */ + full_text?: string | null; +} diff --git a/src/models/Notice.ts b/src/models/Notice.ts index fdc2790..8bade97 100644 --- a/src/models/Notice.ts +++ b/src/models/Notice.ts @@ -7,5 +7,11 @@ export interface Notice { description?: string | null; posted_date?: string | null; naics_code?: string | null; + /** Every attachment on the notice, links included. */ + attachment_count?: number | null; + /** Attachments whose `type` is `file`. An attachment of any other type counts only in `attachment_count`, so `file_count + link_count` need not equal it. Null until the notice has been counted: read null as unknown, not zero. Requires Tango API 5.9.0. */ + file_count?: number | null; + /** Attachments whose `type` is `link`. Null until the notice has been counted. Requires Tango API 5.9.0. */ + link_count?: number | null; attachments?: OpportunityAttachment[] | null; } diff --git a/src/models/Opportunity.ts b/src/models/Opportunity.ts index 691fc8d..d5975e1 100644 --- a/src/models/Opportunity.ts +++ b/src/models/Opportunity.ts @@ -6,6 +6,7 @@ export interface OpportunityAttachment { attachment_id?: string | null; resource_id?: string | null; name?: string | null; + /** `file` for a document, `link` for a URL the notice lists. Only a file has extracted text. */ type?: string | null; mime_type?: string | null; file_size?: number | null; @@ -22,6 +23,22 @@ export interface OpportunityAttachment { doc_role_alt?: AttachmentDocRole | null; } +/** + * Counts on an opportunity (the `meta(...)` expand). + * + * `files_count` and `links_count` are null until the opportunity has been counted, so read null as unknown, not zero. + */ +export interface OpportunityMeta { + notices_count?: number | null; + /** Every attachment across the opportunity's notices, links included. */ + attachments_count?: number | null; + /** Attachments whose `type` is `file`. An attachment of any other type counts only in `attachments_count`, so `files_count + links_count` need not equal it. Requires Tango API 5.9.0. */ + files_count?: number | null; + /** Attachments whose `type` is `link`. Requires Tango API 5.9.0. */ + links_count?: number | null; + notice_type?: Record | null; +} + export interface Opportunity { opportunity_id: string; title: string; @@ -32,4 +49,5 @@ export interface Opportunity { naics_code?: string | null; psc_code?: string | null; attachments?: OpportunityAttachment[] | null; + meta?: OpportunityMeta | null; } diff --git a/src/models/index.ts b/src/models/index.ts index af66121..a817e44 100644 --- a/src/models/index.ts +++ b/src/models/index.ts @@ -12,11 +12,12 @@ export type { Department } from "./Department.js"; export type { DibbsRfq, DibbsRfp, DibbsAward, DibbsOrganizationPayload, DibbsAwardeePayload } from "./Dibbs.js"; export type { Entity, EntityBasic } from "./Entity.js"; export type { Exclusion } from "./Exclusion.js"; +export type { FederalRegisterDocument } from "./FederalRegister.js"; export type { Forecast } from "./Forecast.js"; export type { Grant } from "./Grant.js"; export type { Location } from "./Location.js"; export type { Notice } from "./Notice.js"; -export type { AttachmentDocRole, Opportunity, OpportunityAttachment } from "./Opportunity.js"; +export type { AttachmentDocRole, Opportunity, OpportunityAttachment, OpportunityMeta } from "./Opportunity.js"; export type { RecipientProfile } from "./RecipientProfile.js"; export type { SbirTopic, SbirSolicitation } from "./Sbir.js"; export type { diff --git a/src/shapes/explicitSchemas.ts b/src/shapes/explicitSchemas.ts index bb7b71d..88bd74f 100644 --- a/src/shapes/explicitSchemas.ts +++ b/src/shapes/explicitSchemas.ts @@ -5737,6 +5737,319 @@ export const SLED_FORECAST_SCHEMA: FieldSchemaMap = { }, }; +// Federal Register document. Shape-on-demand, so every field is optional. +export const FEDERAL_REGISTER_DOCUMENT_SCHEMA: FieldSchemaMap = { + uuid: { + name: "uuid", + type: "str", + isOptional: true, + isList: false, + nestedModel: null, + }, + document_number: { + name: "document_number", + type: "str", + isOptional: true, + isList: false, + nestedModel: null, + }, + publication_date: { + name: "publication_date", + type: "date", + isOptional: true, + isList: false, + nestedModel: null, + }, + citation: { + name: "citation", + type: "str", + isOptional: true, + isList: false, + nestedModel: null, + }, + title: { + name: "title", + type: "str", + isOptional: true, + isList: false, + nestedModel: null, + }, + type: { + name: "type", + type: "str", + isOptional: true, + isList: false, + nestedModel: null, + }, + subtype: { + name: "subtype", + type: "str", + isOptional: true, + isList: false, + nestedModel: null, + }, + abstract: { + name: "abstract", + type: "str", + isOptional: true, + isList: false, + nestedModel: null, + }, + action: { + name: "action", + type: "str", + isOptional: true, + isList: false, + nestedModel: null, + }, + dates: { + name: "dates", + type: "str", + isOptional: true, + isList: false, + nestedModel: null, + }, + comments_close_on: { + name: "comments_close_on", + type: "date", + isOptional: true, + isList: false, + nestedModel: null, + }, + effective_on: { + name: "effective_on", + type: "date", + isOptional: true, + isList: false, + nestedModel: null, + }, + signing_date: { + name: "signing_date", + type: "date", + isOptional: true, + isList: false, + nestedModel: null, + }, + start_page: { + name: "start_page", + type: "int", + isOptional: true, + isList: false, + nestedModel: null, + }, + end_page: { + name: "end_page", + type: "int", + isOptional: true, + isList: false, + nestedModel: null, + }, + page_length: { + name: "page_length", + type: "int", + isOptional: true, + isList: false, + nestedModel: null, + }, + volume: { + name: "volume", + type: "int", + isOptional: true, + isList: false, + nestedModel: null, + }, + significant: { + name: "significant", + type: "bool", + isOptional: true, + isList: false, + nestedModel: null, + }, + toc_doc: { + name: "toc_doc", + type: "str", + isOptional: true, + isList: false, + nestedModel: null, + }, + toc_subject: { + name: "toc_subject", + type: "str", + isOptional: true, + isList: false, + nestedModel: null, + }, + correction_of: { + name: "correction_of", + type: "str", + isOptional: true, + isList: false, + nestedModel: null, + }, + corrections: { + name: "corrections", + type: "str", + isOptional: true, + isList: true, + nestedModel: null, + }, + disposition_notes: { + name: "disposition_notes", + type: "str", + isOptional: true, + isList: false, + nestedModel: null, + }, + executive_order_notes: { + name: "executive_order_notes", + type: "str", + isOptional: true, + isList: false, + nestedModel: null, + }, + executive_order_number: { + name: "executive_order_number", + type: "str", + isOptional: true, + isList: false, + nestedModel: null, + }, + presidential_document_number: { + name: "presidential_document_number", + type: "str", + isOptional: true, + isList: false, + nestedModel: null, + }, + proclamation_number: { + name: "proclamation_number", + type: "str", + isOptional: true, + isList: false, + nestedModel: null, + }, + not_received_for_publication: { + name: "not_received_for_publication", + type: "str", + isOptional: true, + isList: false, + nestedModel: null, + }, + comment_url: { + name: "comment_url", + type: "str", + isOptional: true, + isList: false, + nestedModel: null, + }, + regulations_dot_gov_url: { + name: "regulations_dot_gov_url", + type: "str", + isOptional: true, + isList: false, + nestedModel: null, + }, + html_url: { + name: "html_url", + type: "str", + isOptional: true, + isList: false, + nestedModel: null, + }, + body_html_url: { + name: "body_html_url", + type: "str", + isOptional: true, + isList: false, + nestedModel: null, + }, + pdf_url: { + name: "pdf_url", + type: "str", + isOptional: true, + isList: false, + nestedModel: null, + }, + public_inspection_pdf_url: { + name: "public_inspection_pdf_url", + type: "str", + isOptional: true, + isList: false, + nestedModel: null, + }, + raw_text_url: { + name: "raw_text_url", + type: "str", + isOptional: true, + isList: false, + nestedModel: null, + }, + agencies: { + name: "agencies", + type: "dict", + isOptional: true, + isList: true, + nestedModel: null, + }, + cfr_references: { + name: "cfr_references", + type: "dict", + isOptional: true, + isList: true, + nestedModel: null, + }, + dockets: { + name: "dockets", + type: "dict", + isOptional: true, + isList: true, + nestedModel: null, + }, + docket_ids: { + name: "docket_ids", + type: "str", + isOptional: true, + isList: true, + nestedModel: null, + }, + topics: { + name: "topics", + type: "str", + isOptional: true, + isList: true, + nestedModel: null, + }, + regulation_id_numbers: { + name: "regulation_id_numbers", + type: "str", + isOptional: true, + isList: true, + nestedModel: null, + }, + regulation_id_number_info: { + name: "regulation_id_number_info", + type: "dict", + isOptional: true, + isList: false, + nestedModel: null, + }, + regulations_dot_gov_info: { + name: "regulations_dot_gov_info", + type: "dict", + isOptional: true, + isList: false, + nestedModel: null, + }, + // Served by the detail endpoint only, and only when named in the shape. + full_text: { + name: "full_text", + type: "str", + isOptional: true, + isList: false, + nestedModel: null, + }, +}; + export const EXPLICIT_SCHEMAS: ExplicitSchemas = { Office: OFFICE_SCHEMA, Location: LOCATION_SCHEMA, @@ -5765,6 +6078,7 @@ export const EXPLICIT_SCHEMAS: ExplicitSchemas = { Protest: PROTEST_SCHEMA, ProtestDocket: PROTEST_DOCKET_SCHEMA, ContractAppeal: CONTRACT_APPEAL_SCHEMA, + FederalRegisterDocument: FEDERAL_REGISTER_DOCUMENT_SCHEMA, Agency: AGENCY_SCHEMA, Grant: GRANT_SCHEMA, Vehicle: VEHICLE_SCHEMA, diff --git a/src/shapes/generatedOverlay.ts b/src/shapes/generatedOverlay.ts index 0f90a9c..6e913a7 100644 --- a/src/shapes/generatedOverlay.ts +++ b/src/shapes/generatedOverlay.ts @@ -144,6 +144,15 @@ export const GENERATED_NESTED: Record = { total_contract_value: f("total_contract_value", "Decimal"), transactions: f("transactions", "str"), }, + Awards2: { + award_amount: f("award_amount", "str"), + award_date: f("award_date", "date"), + award_number: f("award_number", "str"), + awardee: f("awardee", "str"), + awardee_uei: f("awardee_uei", "str"), + notice_id: f("notice_id", "str"), + opportunity_id: f("opportunity_id", "str"), + }, BudgetAppropriation: { cgac: f("cgac", "int"), fiscal_year: f("fiscal_year", "int"), @@ -370,6 +379,8 @@ export const GENERATED_NESTED: Record = { }, Meta2: { attachments_count: f("attachments_count", "int"), + files_count: f("files_count", "int"), + links_count: f("links_count", "int"), notice_type: f("notice_type", "dict", false, "NoticeType"), notices_count: f("notices_count", "int"), }, @@ -657,6 +668,7 @@ export const GENERATED_OVERLAY: Record = { organization_id: f("organization_id", "str"), }, BudgetAccount: { + account_category: f("account_category", "str"), account_narrative_excerpt: f("account_narrative_excerpt", "str"), account_title: f("account_title", "str"), actual_vs_requested_contract: f("actual_vs_requested_contract", "str"), @@ -690,6 +702,7 @@ export const GENERATED_OVERLAY: Record = { contract_share_of_obligated: f("contract_share_of_obligated", "str"), contract_share_of_obligated_capped: f("contract_share_of_obligated_capped", "str"), created: f("created", "datetime"), + data_through_period: f("data_through_period", "int"), enacted_ba: f("enacted_ba", "str"), enacted_ba_5yr_cagr: f("enacted_ba_5yr_cagr", "str"), enacted_ba_yoy_pct: f("enacted_ba_yoy_pct", "str"), @@ -715,6 +728,8 @@ export const GENERATED_OVERLAY: Record = { obligated_total: f("obligated_total", "str"), obligated_yoy_pct: f("obligated_yoy_pct", "str"), on_off_budget: f("on_off_budget", "str"), + organization: f("organization", "dict", false, "AwardingOffice"), + organization_id: f("organization_id", "str"), outlayed_to_obligated_pct: f("outlayed_to_obligated_pct", "str"), outlayed_to_obligated_pct_capped: f("outlayed_to_obligated_pct_capped", "str"), outlayed_to_obligated_pct_capped_flag: f("outlayed_to_obligated_pct_capped_flag", "str"), @@ -722,6 +737,7 @@ export const GENERATED_OVERLAY: Record = { requested_ba: f("requested_ba", "str"), requested_contractual_services: f("requested_contractual_services", "str"), requested_personnel_share: f("requested_personnel_share", "str"), + source_anomalies: f("source_anomalies", "dict", true), subfunction_code: f("subfunction_code", "str"), top_contract_recipients: f("top_contract_recipients", "str"), top_grant_recipients: f("top_grant_recipients", "str"), @@ -801,6 +817,10 @@ export const GENERATED_OVERLAY: Record = { uuid: f("uuid", "str"), workforce: f("workforce", "dict", false, "Workforce"), }, + Exclusion: { + organization: f("organization", "dict", false, "AwardingOffice"), + organization_id: f("organization_id", "str"), + }, Forecast: { created: f("created", "datetime"), display: f("display", "dict", false, "Display"), @@ -903,6 +923,12 @@ export const GENERATED_OVERLAY: Record = { address: f("address", "dict", false, "Address"), archive: f("archive", "dict", false, "Archive"), attachments: f("attachments", "dict", true, "Attachments2"), + award_amount: f("award_amount", "str"), + award_date: f("award_date", "date"), + awardee: f("awardee", "str"), + awardee_uei: f("awardee_uei", "str"), + file_count: f("file_count", "int"), + link_count: f("link_count", "int"), meta: f("meta", "dict", false, "Meta"), office: f("office", "dict", false, "AwardingOffice"), opportunity: f("opportunity", "dict", false, "Opportunity2"), @@ -967,6 +993,13 @@ export const GENERATED_OVERLAY: Record = { agency_id: f("agency_id", "str"), archive_date: f("archive_date", "date"), attachments: f("attachments", "dict", true, "Attachments5"), + award_amount: f("award_amount", "str"), + award_count: f("award_count", "int"), + award_date: f("award_date", "date"), + awarded: f("awarded", "bool"), + awardee: f("awardee", "str"), + awardee_uei: f("awardee_uei", "str"), + awards: f("awards", "dict", true, "Awards2"), department: f("department", "dict", false, "Department3"), department_id: f("department_id", "str"), latest_notice: f("latest_notice", "dict", false, "LatestNotice"), @@ -978,6 +1011,7 @@ export const GENERATED_OVERLAY: Record = { secondary_contact: f("secondary_contact", "dict", false, "PrimaryContact"), set_aside: f("set_aside", "dict", false, "CodeDescription"), snippet: f("snippet", "str"), + solicitation_opportunity_id: f("solicitation_opportunity_id", "str"), }, Organization: { aac_code: f("aac_code", "str"), @@ -1046,7 +1080,16 @@ export const GENERATED_OVERLAY: Record = { outcome_reason: f("outcome_reason", "str"), size_standard: f("size_standard", "str"), }, + SbirSolicitation: { + organization: f("organization", "dict", false, "AwardingOffice"), + }, + SbirTopic: { + cycle_name: f("cycle_name", "str"), + organization: f("organization", "dict", false, "AwardingOffice"), + }, Vehicle: { + holder_count: f("holder_count", "int"), name: f("name", "str"), + order_winner_count: f("order_winner_count", "int"), }, }; diff --git a/tests/unit/client.federal-register.test.ts b/tests/unit/client.federal-register.test.ts new file mode 100644 index 0000000..381206a --- /dev/null +++ b/tests/unit/client.federal-register.test.ts @@ -0,0 +1,189 @@ +/** + * Tests for the Federal Register endpoint (`/api/federal_register/`). + * + * Covers the request contract (path, filters under the API's own param names, default shapes, the uuid detail route) via the injected fetchImpl mock, plus the shape schema the SDK registers for the resource. + */ + +import { readFileSync } from "node:fs"; +import { TangoClient } from "../../src/client.js"; +import { ShapeConfig } from "../../src/config.js"; +import type { FederalRegisterDocument } from "../../src/models/index.js"; +import { SchemaRegistry } from "../../src/shapes/schema.js"; + +type RecordedCall = { url: string }; + +function makeClient(body: unknown = { count: 0, next: null, previous: null, results: [] }): { client: TangoClient; calls: RecordedCall[] } { + const calls: RecordedCall[] = []; + const fetchImpl = (async (url: string | URL) => { + calls.push({ url: String(url) }); + return { + ok: true, + status: 200, + async text() { + return JSON.stringify(body); + }, + }; + }) as unknown as typeof fetch; + const client = new TangoClient({ apiKey: "k", baseUrl: "http://localhost:8000", fetchImpl, retries: 0 }); + return { client, calls }; +} + +function params(calls: RecordedCall[]): URLSearchParams { + return new URL(calls[0].url).searchParams; +} + +const UUID = "0b9d2c1e-5f4a-4c8b-9a37-2d6e8f1a4b90"; + +const DOCUMENT: FederalRegisterDocument = { + uuid: UUID, + document_number: "2026-04117", + publication_date: "2026-03-02", + type: "Proposed Rule", + subtype: null, + title: "Air Plan Approval; Example State; Regional Haze", + abstract: "The agency proposes to approve a revision to the state implementation plan.", + action: "Proposed rule.", + agencies: [{ name: "Environmental Protection Agency", slug: "environmental-protection-agency" }], + cfr_references: [{ title: 40, part: 52 }], + citation: "91 FR 10234", + significant: false, + comments_close_on: "2026-04-01", + effective_on: null, + html_url: "https://example.gov/documents/2026-04117", + pdf_url: "https://example.gov/documents/2026-04117.pdf", +}; + +describe("TangoClient: Federal Register documents", () => { + it("listFederalRegisterDocuments sends every filter under the API's param name", async () => { + const { client, calls } = makeClient(); + await client.listFederalRegisterDocuments({ + search: '"regional haze"', + document_number: "2026-04117|2016-31922", + type: "Proposed Rule", + agency: "EPA", + fr_agency: "environmental-protection-agency", + publication_date_after: "2026-01-01", + publication_date_before: "2026-06-30", + effective_on_after: "2026-02-01", + effective_on_before: "2026-07-31", + comments_close_on_after: "2026-03-01", + comments_close_on_before: "2026-08-31", + comments_open: true, + cfr_title: 40, + cfr_part: "52", + significant: true, + rin: "2060-AV16", + executive_order_number: "14110", + ordering: "-comments_close_on", + limit: 10, + }); + + expect(new URL(calls[0].url).pathname).toBe("/api/federal_register/"); + const p = params(calls); + expect(Object.fromEntries([...p.entries()].filter(([k]) => !["page", "limit", "shape"].includes(k)))).toEqual({ + search: '"regional haze"', + document_number: "2026-04117|2016-31922", + type: "Proposed Rule", + agency: "EPA", + fr_agency: "environmental-protection-agency", + publication_date_after: "2026-01-01", + publication_date_before: "2026-06-30", + effective_on_after: "2026-02-01", + effective_on_before: "2026-07-31", + comments_close_on_after: "2026-03-01", + comments_close_on_before: "2026-08-31", + comments_open: "true", + cfr_title: "40", + cfr_part: "52", + significant: "true", + rin: "2060-AV16", + executive_order_number: "14110", + ordering: "-comments_close_on", + }); + expect(p.get("limit")).toBe("10"); + }); + + it("sends comments_open=false and significant=false rather than dropping them", async () => { + const { client, calls } = makeClient(); + await client.listFederalRegisterDocuments({ comments_open: false, significant: false }); + expect(params(calls).get("comments_open")).toBe("false"); + expect(params(calls).get("significant")).toBe("false"); + }); + + it("defaults the list to FEDERAL_REGISTER_MINIMAL", async () => { + const { client, calls } = makeClient(); + await client.listFederalRegisterDocuments(); + expect(params(calls).get("shape")).toBe(ShapeConfig.FEDERAL_REGISTER_MINIMAL); + }); + + it("parses dates and keeps the Federal Register's own structures as served", async () => { + const { client } = makeClient({ count: 1, next: null, previous: null, results: [DOCUMENT] }); + const page = await client.listFederalRegisterDocuments({ type: "Proposed Rule" }); + const row = page.results[0]; + + expect(page.count).toBe(1); + expect(row.document_number).toBe("2026-04117"); + expect(row.publication_date).toEqual(new Date("2026-03-02")); + expect(row.comments_close_on).toEqual(new Date("2026-04-01")); + expect(row.effective_on).toBeNull(); + expect(row.significant).toBe(false); + expect(row.agencies).toEqual(DOCUMENT.agencies); + expect(row.cfr_references).toEqual(DOCUMENT.cfr_references); + }); + + it("getFederalRegisterDocument uses the uuid route and the comprehensive default", async () => { + const { client, calls } = makeClient(DOCUMENT); + const doc = await client.getFederalRegisterDocument(UUID); + + expect(new URL(calls[0].url).pathname).toBe(`/api/federal_register/${UUID}/`); + expect(params(calls).get("shape")).toBe(ShapeConfig.FEDERAL_REGISTER_COMPREHENSIVE); + expect(doc.title).toBe(DOCUMENT.title); + }); + + it("getFederalRegisterDocument accepts full_text when the shape names it", async () => { + const { client, calls } = makeClient({ uuid: UUID, title: DOCUMENT.title, full_text: "ENVIRONMENTAL PROTECTION AGENCY ..." }); + const doc = await client.getFederalRegisterDocument(UUID, { shape: "uuid,title,full_text" }); + + expect(params(calls).get("shape")).toBe("uuid,title,full_text"); + expect(doc.full_text).toBe("ENVIRONMENTAL PROTECTION AGENCY ..."); + }); + + it("getFederalRegisterDocument rejects an empty uuid before issuing a request", async () => { + const { client, calls } = makeClient(DOCUMENT); + await expect(client.getFederalRegisterDocument("")).rejects.toThrow("uuid is required"); + expect(calls).toHaveLength(0); + }); +}); + +describe("FederalRegisterDocument shape schema", () => { + const registry = new SchemaRegistry(); + const contract = JSON.parse(readFileSync(new URL("../../contracts/filter_shape_contract.json", import.meta.url), "utf8")); + const contractFields: string[] = contract.resources.federal_register.runtime.shape.fields; + + it("the vendored contract lists the resource's shape fields", () => { + expect(contractFields.length).toBeGreaterThanOrEqual(43); + expect(contractFields).toContain("regulation_id_numbers"); + }); + + it("knows every field the contract serves, plus the detail-only full_text", () => { + const known = registry.getSchema("FederalRegisterDocument").fields; + expect(contractFields.concat("full_text").filter((field) => known[field] === undefined)).toEqual([]); + }); + + it.each(["FEDERAL_REGISTER_MINIMAL", "FEDERAL_REGISTER_COMPREHENSIVE"] as const)("%s names only fields the contract serves", (preset) => { + expect(ShapeConfig[preset].split(",").filter((field) => !contractFields.includes(field))).toEqual([]); + }); + + it("neither default shape asks for full_text", () => { + expect(ShapeConfig.FEDERAL_REGISTER_MINIMAL.split(",")).not.toContain("full_text"); + expect(ShapeConfig.FEDERAL_REGISTER_COMPREHENSIVE.split(",")).not.toContain("full_text"); + }); + + it.each(["agencies", "cfr_references", "dockets", "docket_ids", "topics", "regulation_id_numbers", "corrections"])("%s is a list", (field) => { + expect(registry.getField("FederalRegisterDocument", field).isList).toBe(true); + }); + + it.each(["agency", "fr_agency", "rin", "comments_open"])("%s is a filter and never a response field", (field) => { + expect(registry.getSchema("FederalRegisterDocument").fields[field]).toBeUndefined(); + }); +}); diff --git a/tests/unit/client.shape-parity.test.ts b/tests/unit/client.shape-parity.test.ts index faeae14..5f69e64 100644 --- a/tests/unit/client.shape-parity.test.ts +++ b/tests/unit/client.shape-parity.test.ts @@ -94,3 +94,79 @@ describe("SLED delisting and jurisdiction provenance", () => { expect((row.meta as Record).jurisdiction_declared).toBe(false); }); }); + +describe("attachment file and link counts (Tango API 5.9.0)", () => { + const OPPORTUNITY_SHAPE = "opportunity_id,meta(attachments_count,files_count,links_count)"; + const NOTICE_SHAPE = "notice_id,attachment_count,file_count,link_count"; + + it("getOpportunity shapes meta(files_count,links_count) beside attachments_count", async () => { + const record = { opportunity_id: "opp-1", meta: { attachments_count: 6, files_count: 4, links_count: 1 } }; + const { client, calls } = makeClient(record); + const opp = await client.getOpportunity("opp-1", { shape: OPPORTUNITY_SHAPE }); + + expect(new URL(calls[0].url).searchParams.get("shape")).toBe(OPPORTUNITY_SHAPE); + expect(opp.meta).toEqual({ attachments_count: 6, files_count: 4, links_count: 1 }); + }); + + it("listOpportunities keeps an uncounted opportunity's counts null rather than zero", async () => { + const record = { opportunity_id: "opp-2", meta: { attachments_count: 3, files_count: null, links_count: null } }; + const { client } = makeClient(page(record)); + const resp = await client.listOpportunities({ shape: OPPORTUNITY_SHAPE }); + + expect(resp.results[0].meta).toEqual({ attachments_count: 3, files_count: null, links_count: null }); + }); + + it("getNotice shapes file_count and link_count beside attachment_count", async () => { + const record = { notice_id: "notice-1", attachment_count: 6, file_count: 4, link_count: 1 }; + const { client, calls } = makeClient(record); + const notice = await client.getNotice("notice-1", { shape: NOTICE_SHAPE }); + + expect(new URL(calls[0].url).searchParams.get("shape")).toBe(NOTICE_SHAPE); + expect(notice).toMatchObject(record); + }); + + it("listNotices keeps an uncounted notice's counts null rather than zero", async () => { + const record = { notice_id: "notice-2", attachment_count: 3, file_count: null, link_count: null }; + const { client } = makeClient(page(record)); + const resp = await client.listNotices({ shape: NOTICE_SHAPE }); + + expect(resp.results[0]).toMatchObject(record); + }); +}); + +describe("fields the API added between 5.3.0 and 5.8.0", () => { + it("listOpportunities shapes the award leaves and the awards expand", async () => { + const shape = "opportunity_id,awarded,award_count,awardee_uei,awards(award_number,award_date,awardee_uei)"; + const record = { + opportunity_id: "opp-1", + awarded: true, + award_count: 2, + awardee_uei: "ABCDEFGHJKL1", + awards: [ + { award_number: "W912DY26C0001", award_date: "2026-05-04", awardee_uei: "ABCDEFGHJKL1" }, + { award_number: "W912DY26C0002", award_date: "2026-05-11", awardee_uei: "ZYXWVUTSRQP9" }, + ], + }; + const { client } = makeClient(page(record)); + const row = (await client.listOpportunities({ shape })).results[0]; + + expect(row.awarded).toBe(true); + expect(row.award_count).toBe(2); + expect(row.awards).toEqual([ + { award_number: "W912DY26C0001", award_date: new Date("2026-05-04"), awardee_uei: "ABCDEFGHJKL1" }, + { award_number: "W912DY26C0002", award_date: new Date("2026-05-11"), awardee_uei: "ZYXWVUTSRQP9" }, + ]); + }); + + it.each([ + ["listExclusions", "exclusion_key,organization_id,organization(agency_name,department_name)"], + ["listSbirTopics", "topic_id,cycle_name,organization(agency_code,agency_name)"], + ["listSbirSolicitations", "solicitation_id,organization(organization_id,agency_name)"], + ["listVehicles", "uuid,holder_count,order_winner_count"], + ] as const)("%s accepts %s", async (method, shape) => { + const { client, calls } = makeClient(page({})); + await client[method]({ shape }); + + expect(new URL(calls[0].url).searchParams.get("shape")).toBe(shape); + }); +}); diff --git a/tests/unit/client.typed-filters.test.ts b/tests/unit/client.typed-filters.test.ts new file mode 100644 index 0000000..b35112f --- /dev/null +++ b/tests/unit/client.typed-filters.test.ts @@ -0,0 +1,91 @@ +/** + * Filters the API added between 5.3.0 and 5.8.0, declared as typed options and sent under the API's own param names. + */ + +import { TangoClient } from "../../src/client.js"; + +function makeClient(): { client: TangoClient; urls: string[] } { + const urls: string[] = []; + const fetchImpl = (async (url: string | URL) => { + urls.push(String(url)); + return { + ok: true, + status: 200, + async text() { + return JSON.stringify({ count: 0, next: null, previous: null, results: [] }); + }, + }; + }) as unknown as typeof fetch; + const client = new TangoClient({ apiKey: "k", baseUrl: "http://localhost:8000", fetchImpl, retries: 0 }); + return { client, urls }; +} + +function sent(urls: string[]): { path: string; params: URLSearchParams } { + const url = new URL(urls[0]); + return { path: url.pathname, params: url.searchParams }; +} + +describe("typed filters added through Tango API 5.8.0", () => { + it("listOpportunities sends awarded and awardee_uei", async () => { + const { client, urls } = makeClient(); + await client.listOpportunities({ awarded: true, awardee_uei: "ABCDEFGHJKL1|ZYXWVUTSRQP9" }); + const { path, params } = sent(urls); + expect(path).toBe("/api/opportunities/"); + expect(params.get("awarded")).toBe("true"); + expect(params.get("awardee_uei")).toBe("ABCDEFGHJKL1|ZYXWVUTSRQP9"); + }); + + it("listOpportunities sends awarded=false rather than dropping it", async () => { + const { client, urls } = makeClient(); + await client.listOpportunities({ awarded: false }); + expect(sent(urls).params.get("awarded")).toBe("false"); + }); + + it("listExclusions sends agency", async () => { + const { client, urls } = makeClient(); + await client.listExclusions({ agency: "EPA|GSA" }); + const { path, params } = sent(urls); + expect(path).toBe("/api/exclusions/"); + expect(params.get("agency")).toBe("EPA|GSA"); + }); + + it("listItDashboard sends agency", async () => { + const { client, urls } = makeClient(); + await client.listItDashboard({ agency: "Department of Energy" }); + const { path, params } = sent(urls); + expect(path).toBe("/api/itdashboard/"); + expect(params.get("agency")).toBe("Department of Energy"); + }); + + it("listSbirTopics sends cycle_name", async () => { + const { client, urls } = makeClient(); + await client.listSbirTopics({ cycle_name: "DOD_SBIR_2026_P1_CBZ" }); + const { path, params } = sent(urls); + expect(path).toBe("/api/sbir/topics/"); + expect(params.get("cycle_name")).toBe("DOD_SBIR_2026_P1_CBZ"); + }); + + it("listBudgetAccounts sends agency, account category and data_through_period filters with their dunder names", async () => { + const { client, urls } = makeClient(); + await client.listBudgetAccounts({ + agency: "EPA", + account_category: "budgetary", + account_category__in: "budgetary,credit_financing", + data_through_period: 12, + data_through_period__gte: 6, + data_through_period__lte: 11, + data_through_period__isnull: false, + }); + const { path, params } = sent(urls); + expect(path).toBe("/api/budget/accounts/"); + expect(Object.fromEntries([...params.entries()].filter(([k]) => !["page", "limit"].includes(k)))).toEqual({ + agency: "EPA", + account_category: "budgetary", + account_category__in: "budgetary,credit_financing", + data_through_period: "12", + data_through_period__gte: "6", + data_through_period__lte: "11", + data_through_period__isnull: "false", + }); + }); +});