diff --git a/CHANGELOG.md b/CHANGELOG.md index 50da30f..3efe417 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,16 @@ This project follows [Semantic Versioning](https://semver.org/). ## [Unreleased] +### Added + +- **Financial-assistance awards** (Tango API 5.12.0). USAspending's grant, loan, direct-payment and other assistance awards, one endpoint per group under `/api/assistance/`. Each group gets a cursor-paginated list, a get by `key`, and the award's `/transactions/` and `/listings/` sub-routes: `listAssistanceGrants` / `getAssistanceGrant` / `listAssistanceGrantTransactions` / `listAssistanceGrantListings`, and the same four for `Loan(s)`, `DirectPayment(s)` and `Other`, plus `iterateAssistanceGrants`, `iterateAssistanceLoans`, `iterateAssistanceDirectPayments` and `iterateAssistanceOther`. All 30 of the API's filters are typed on `ListAssistanceAwardsOptions` under the API's own names, including `fain`, `uri`, `cfda_number`, `assistance_type`, `record_type`, `disaster_emergency_fund_code`, `funding_opportunity_number`, `pop_state` / `pop_country` / `pop_zip` and `obligated_gte` / `obligated_lte`. The four groups share one `AssistanceAward` model interface and shape schema, and the `ShapeConfig.ASSISTANCE_AWARDS_MINIMAL` default, which is the API's own default shape. + + The lists are keyset-only, so they take a `cursor` and ignore `page`; the sub-routes are page-numbered, with transactions capped at 500 per page and listings at 100. A retired award key still resolves to the award that carries it now. To compare dollar amounts across groups, use `pragmatic_obligations`: the obligation for grants and payments, the subsidy cost for loans. These are awards already obligated; grant opportunities remain `listGrants()`. + +### Changed + +- The vendored API contract (`contracts/filter_shape_contract.json`) moves from Tango API 5.11.0 to 5.12.1. The four assistance resources are the only difference. + ## [1.4.1] - 2026-10-19 ### Changed diff --git a/README.md b/README.md index 6135633..bce6764 100644 --- a/README.md +++ b/README.md @@ -186,6 +186,13 @@ The Node.js client mirrors the Python SDK's high-level API. Selected highlights: - `listOtas(options)` / `getOta(key)` / `listOtidvs(options)` / `getOtidv(key)` / `listOtidvAwards(key, options)` - `listSubawards(options)` / `getSubaward(key)` +**Financial-Assistance Awards** (grants, loans, direct payments, other) + +- `listAssistanceGrants(options)` / `getAssistanceGrant(key, options)` / `listAssistanceGrantTransactions(key, options)` / `listAssistanceGrantListings(key, options)` +- `listAssistanceLoans(options)` / `getAssistanceLoan(key, options)` / `listAssistanceLoanTransactions(key, options)` / `listAssistanceLoanListings(key, options)` +- `listAssistanceDirectPayments(options)` / `getAssistanceDirectPayment(key, options)` / `listAssistanceDirectPaymentTransactions(key, options)` / `listAssistanceDirectPaymentListings(key, options)` +- `listAssistanceOther(options)` / `getAssistanceOther(key, options)` / `listAssistanceOtherTransactions(key, options)` / `listAssistanceOtherListings(key, options)` + **Vehicles** - `listVehicles(options)` / `getVehicle(uuid, options)` / `listVehicleAwardees(uuid, options)` / `listVehicleOrders(uuid, options)` @@ -258,6 +265,7 @@ The Node.js client mirrors the Python SDK's high-level API. Selected highlights: - `iterateGrants` / `iterateForecasts` / `iterateIdvs` / `iterateVehicles` - `iterateDibbsRfqs` / `iterateDibbsRfps` / `iterateDibbsAwards` - `iterateExclusions` / `iterateSbirTopics` / `iterateSbirSolicitations` +- `iterateAssistanceGrants` / `iterateAssistanceLoans` / `iterateAssistanceDirectPayments` / `iterateAssistanceOther` **Utility** diff --git a/contracts/filter_shape_contract.json b/contracts/filter_shape_contract.json index fed5961..6fdd5e1 100644 --- a/contracts/filter_shape_contract.json +++ b/contracts/filter_shape_contract.json @@ -1,6 +1,6 @@ { "meta": { - "api_version": "5.11.0", + "api_version": "5.12.1", "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 @@ -1596,6 +1596,2230 @@ "swagger_has_key": false, "swagger_params": [] }, + "assistance/direct_payments": { + "basename": "assistance-direct_payments", + "docs_file": null, + "docs_params": [], + "prefix": "assistance/direct_payments", + "resource_key": "assistance/direct_payments", + "runtime": { + "filter_params": [ + "assistance_type", + "award_date", + "award_date_gte", + "award_date_lte", + "awarding_agency", + "business_funds_indicator", + "cfda_number", + "disaster_emergency_fund_code", + "fain", + "fiscal_year", + "fiscal_year_gte", + "fiscal_year_lte", + "funding_agency", + "funding_opportunity_number", + "key", + "obligated_gte", + "obligated_lte", + "ordering", + "pop_country", + "pop_end_date_gte", + "pop_end_date_lte", + "pop_start_date_gte", + "pop_start_date_lte", + "pop_state", + "pop_zip", + "recipient", + "record_type", + "search", + "uei", + "uri" + ], + "filter_params_detail": { + "assistance_type": { + "filter_class": "BaseSmartFilter", + "type": "string" + }, + "award_date": { + "filter_class": "DateFilter", + "type": "date" + }, + "award_date_gte": { + "filter_class": "DateFilter", + "type": "date" + }, + "award_date_lte": { + "filter_class": "DateFilter", + "type": "date" + }, + "awarding_agency": { + "filter_class": "PerformantAgencyFilter", + "type": "string" + }, + "business_funds_indicator": { + "filter_class": "BaseSmartFilter", + "type": "string" + }, + "cfda_number": { + "filter_class": "IdListFilter", + "type": "string" + }, + "disaster_emergency_fund_code": { + "filter_class": "BaseSmartFilter", + "type": "string" + }, + "fain": { + "filter_class": "IdListFilter", + "type": "string" + }, + "fiscal_year": { + "filter_class": "NumberFilter", + "type": "number" + }, + "fiscal_year_gte": { + "filter_class": "NumberFilter", + "type": "number" + }, + "fiscal_year_lte": { + "filter_class": "NumberFilter", + "type": "number" + }, + "funding_agency": { + "filter_class": "PerformantAgencyFilter", + "type": "string" + }, + "funding_opportunity_number": { + "filter_class": "IdListFilter", + "type": "string" + }, + "key": { + "filter_class": "IdListFilter", + "type": "string" + }, + "obligated_gte": { + "filter_class": "NumberFilter", + "lookup": "gte", + "type": "number" + }, + "obligated_lte": { + "filter_class": "NumberFilter", + "lookup": "lte", + "type": "number" + }, + "ordering": { + "choices": [ + "-award_date", + "-obligated", + "-pragmatic_obligations", + "award_date", + "obligated", + "pragmatic_obligations" + ], + "choices_count": 6, + "filter_class": "OrderingFilter", + "type": "ordering" + }, + "pop_country": { + "filter_class": "BaseSmartFilter", + "type": "string" + }, + "pop_end_date_gte": { + "filter_class": "DateFilter", + "lookup": "gte", + "type": "date" + }, + "pop_end_date_lte": { + "filter_class": "DateFilter", + "lookup": "lte", + "type": "date" + }, + "pop_start_date_gte": { + "filter_class": "DateFilter", + "lookup": "gte", + "type": "date" + }, + "pop_start_date_lte": { + "filter_class": "DateFilter", + "lookup": "lte", + "type": "date" + }, + "pop_state": { + "filter_class": "BaseSmartFilter", + "type": "string" + }, + "pop_zip": { + "filter_class": "BaseSmartFilter", + "type": "string" + }, + "recipient": { + "filter_class": "CharFilter", + "type": "string" + }, + "record_type": { + "filter_class": "BaseSmartFilter", + "type": "string" + }, + "search": { + "filter_class": "CharFilter", + "type": "string" + }, + "uei": { + "filter_class": "UeiToEntityUuidFilter", + "type": "string" + }, + "uri": { + "filter_class": "IdListFilter", + "type": "string" + } + }, + "ordering_aliases": [], + "ordering_fields": [ + "award_date", + "obligated", + "pragmatic_obligations" + ], + "pagination": { + "class": "AssistanceKeysetPagination", + "max_page_size": 100 + }, + "shape": { + "expands": { + "assistance_listing": { + "expands": {}, + "fields": [ + "number", + "title" + ] + }, + "assistance_type": { + "expands": {}, + "fields": [ + "code", + "description" + ] + }, + "awarding_office": { + "expands": {}, + "fields": [ + "agency_code", + "agency_name", + "department_code", + "department_name", + "office_code", + "office_name", + "organization_id" + ] + }, + "funding_office": { + "expands": {}, + "fields": [ + "agency_code", + "agency_name", + "department_code", + "department_name", + "office_code", + "office_name", + "organization_id" + ] + }, + "listings": { + "expands": {}, + "fields": [ + "cfda_number", + "face_value_of_loan", + "first_action_date", + "indirect_cost_federal_share_amount", + "is_primary", + "last_action_date", + "loan_subsidy_cost", + "non_federal_funding_amount", + "obligated", + "pragmatic_obligations", + "transactions_count" + ] + }, + "obligations_by_fiscal_year": { + "expands": {}, + "fields": [ + "fiscal_year", + "obligated" + ] + }, + "officers": { + "expands": {}, + "fields": [ + "highly_compensated_officer_1_amount", + "highly_compensated_officer_1_name", + "highly_compensated_officer_2_amount", + "highly_compensated_officer_2_name", + "highly_compensated_officer_3_amount", + "highly_compensated_officer_3_name", + "highly_compensated_officer_4_amount", + "highly_compensated_officer_4_name", + "highly_compensated_officer_5_amount", + "highly_compensated_officer_5_name" + ] + }, + "period_of_performance": { + "expands": {}, + "fields": [ + "current_end_date", + "start_date" + ] + }, + "place_of_performance": { + "expands": {}, + "fields": [ + "city_name", + "code", + "country", + "county_fips", + "county_name", + "foreign_location", + "scope", + "state_code", + "state_fips", + "state_name", + "zip_code", + "zip_plus_four" + ] + }, + "recipient": { + "expands": {}, + "fields": [ + "business_types", + "congressional_district", + "congressional_district_original", + "county", + "duns", + "highest_owner", + "legal_business_name", + "legal_business_name_raw", + "uei", + "uuid" + ] + }, + "recipient_address": { + "expands": {}, + "fields": [ + "address_line1", + "address_line2", + "city", + "congressional_district_current", + "congressional_district_original", + "country_code", + "county_fips", + "foreign_city_name", + "foreign_postal_code", + "foreign_province_name", + "state_fips", + "state_or_province_code", + "zip_code", + "zip_code_plus4" + ] + }, + "transactions": { + "expands": {}, + "fields": [ + "action_date_fiscal_year", + "action_type", + "assistance_type_code", + "award_amount_basis", + "award_recipient_basis", + "cfda_number", + "correction_delete_indicator", + "face_value_of_loan", + "indirect_cost_federal_share_amount", + "initial_report_date", + "key", + "last_modified_date", + "loan_subsidy_cost", + "modification_number", + "non_federal_funding_amount", + "obligated", + "pragmatic_obligations", + "transaction_date" + ] + } + }, + "fields": [ + "assistance_type", + "assistance_types_count", + "award_amount_basis", + "award_date", + "award_recipient_basis", + "award_total_face_value_of_loan", + "award_total_loan_subsidy_cost", + "award_total_non_federal_funding", + "award_total_obligated", + "award_total_outlayed", + "business_funds_indicator", + "cfda_number", + "covid19_obligated", + "covid19_outlayed", + "disaster_emergency_fund_codes", + "face_value_of_loan", + "fain", + "federal_accounts_funding_this_award", + "fiscal_year", + "former_award_keys", + "funding_opportunity_goals_text", + "funding_opportunity_number", + "iija_obligated", + "iija_outlayed", + "indirect_cost_federal_share_amount", + "insurance_coverage_amount", + "key", + "last_action_date", + "last_modified_date", + "listings_count", + "loan_subsidy_cost", + "non_federal_funding_amount", + "object_classes_funding_this_award", + "obligated", + "pragmatic_obligations", + "program_activities_funding_this_award", + "purposes_differ", + "record_type", + "sai_number", + "summary", + "title", + "transactions", + "transactions_count", + "treasury_accounts_funding_this_award", + "uri" + ] + }, + "shape_flat_paths": [ + "assistance_listing", + "assistance_listing.number", + "assistance_listing.title", + "assistance_type", + "assistance_type.code", + "assistance_type.description", + "assistance_types_count", + "award_amount_basis", + "award_date", + "award_recipient_basis", + "award_total_face_value_of_loan", + "award_total_loan_subsidy_cost", + "award_total_non_federal_funding", + "award_total_obligated", + "award_total_outlayed", + "awarding_office", + "awarding_office.agency_code", + "awarding_office.agency_name", + "awarding_office.department_code", + "awarding_office.department_name", + "awarding_office.office_code", + "awarding_office.office_name", + "awarding_office.organization_id", + "business_funds_indicator", + "cfda_number", + "covid19_obligated", + "covid19_outlayed", + "disaster_emergency_fund_codes", + "face_value_of_loan", + "fain", + "federal_accounts_funding_this_award", + "fiscal_year", + "former_award_keys", + "funding_office", + "funding_office.agency_code", + "funding_office.agency_name", + "funding_office.department_code", + "funding_office.department_name", + "funding_office.office_code", + "funding_office.office_name", + "funding_office.organization_id", + "funding_opportunity_goals_text", + "funding_opportunity_number", + "iija_obligated", + "iija_outlayed", + "indirect_cost_federal_share_amount", + "insurance_coverage_amount", + "key", + "last_action_date", + "last_modified_date", + "listings", + "listings.cfda_number", + "listings.face_value_of_loan", + "listings.first_action_date", + "listings.indirect_cost_federal_share_amount", + "listings.is_primary", + "listings.last_action_date", + "listings.loan_subsidy_cost", + "listings.non_federal_funding_amount", + "listings.obligated", + "listings.pragmatic_obligations", + "listings.transactions_count", + "listings_count", + "loan_subsidy_cost", + "non_federal_funding_amount", + "object_classes_funding_this_award", + "obligated", + "obligations_by_fiscal_year", + "obligations_by_fiscal_year.fiscal_year", + "obligations_by_fiscal_year.obligated", + "officers", + "officers.highly_compensated_officer_1_amount", + "officers.highly_compensated_officer_1_name", + "officers.highly_compensated_officer_2_amount", + "officers.highly_compensated_officer_2_name", + "officers.highly_compensated_officer_3_amount", + "officers.highly_compensated_officer_3_name", + "officers.highly_compensated_officer_4_amount", + "officers.highly_compensated_officer_4_name", + "officers.highly_compensated_officer_5_amount", + "officers.highly_compensated_officer_5_name", + "period_of_performance", + "period_of_performance.current_end_date", + "period_of_performance.start_date", + "place_of_performance", + "place_of_performance.city_name", + "place_of_performance.code", + "place_of_performance.country", + "place_of_performance.county_fips", + "place_of_performance.county_name", + "place_of_performance.foreign_location", + "place_of_performance.scope", + "place_of_performance.state_code", + "place_of_performance.state_fips", + "place_of_performance.state_name", + "place_of_performance.zip_code", + "place_of_performance.zip_plus_four", + "pragmatic_obligations", + "program_activities_funding_this_award", + "purposes_differ", + "recipient", + "recipient.business_types", + "recipient.congressional_district", + "recipient.congressional_district_original", + "recipient.county", + "recipient.duns", + "recipient.highest_owner", + "recipient.legal_business_name", + "recipient.legal_business_name_raw", + "recipient.uei", + "recipient.uuid", + "recipient_address", + "recipient_address.address_line1", + "recipient_address.address_line2", + "recipient_address.city", + "recipient_address.congressional_district_current", + "recipient_address.congressional_district_original", + "recipient_address.country_code", + "recipient_address.county_fips", + "recipient_address.foreign_city_name", + "recipient_address.foreign_postal_code", + "recipient_address.foreign_province_name", + "recipient_address.state_fips", + "recipient_address.state_or_province_code", + "recipient_address.zip_code", + "recipient_address.zip_code_plus4", + "record_type", + "sai_number", + "summary", + "title", + "transactions", + "transactions.action_date_fiscal_year", + "transactions.action_type", + "transactions.assistance_type_code", + "transactions.award_amount_basis", + "transactions.award_recipient_basis", + "transactions.cfda_number", + "transactions.correction_delete_indicator", + "transactions.face_value_of_loan", + "transactions.indirect_cost_federal_share_amount", + "transactions.initial_report_date", + "transactions.key", + "transactions.last_modified_date", + "transactions.loan_subsidy_cost", + "transactions.modification_number", + "transactions.non_federal_funding_amount", + "transactions.obligated", + "transactions.pragmatic_obligations", + "transactions.transaction_date", + "transactions_count", + "treasury_accounts_funding_this_award", + "uri" + ], + "shape_gated_by_level": {}, + "shape_supported": true, + "shape_tier_required": null, + "viewset": "assistance.views.DirectPaymentAwardViewSet" + }, + "swagger_has_key": false, + "swagger_params": [] + }, + "assistance/grants": { + "basename": "assistance-grants", + "docs_file": null, + "docs_params": [], + "prefix": "assistance/grants", + "resource_key": "assistance/grants", + "runtime": { + "filter_params": [ + "assistance_type", + "award_date", + "award_date_gte", + "award_date_lte", + "awarding_agency", + "business_funds_indicator", + "cfda_number", + "disaster_emergency_fund_code", + "fain", + "fiscal_year", + "fiscal_year_gte", + "fiscal_year_lte", + "funding_agency", + "funding_opportunity_number", + "key", + "obligated_gte", + "obligated_lte", + "ordering", + "pop_country", + "pop_end_date_gte", + "pop_end_date_lte", + "pop_start_date_gte", + "pop_start_date_lte", + "pop_state", + "pop_zip", + "recipient", + "record_type", + "search", + "uei", + "uri" + ], + "filter_params_detail": { + "assistance_type": { + "filter_class": "BaseSmartFilter", + "type": "string" + }, + "award_date": { + "filter_class": "DateFilter", + "type": "date" + }, + "award_date_gte": { + "filter_class": "DateFilter", + "type": "date" + }, + "award_date_lte": { + "filter_class": "DateFilter", + "type": "date" + }, + "awarding_agency": { + "filter_class": "PerformantAgencyFilter", + "type": "string" + }, + "business_funds_indicator": { + "filter_class": "BaseSmartFilter", + "type": "string" + }, + "cfda_number": { + "filter_class": "IdListFilter", + "type": "string" + }, + "disaster_emergency_fund_code": { + "filter_class": "BaseSmartFilter", + "type": "string" + }, + "fain": { + "filter_class": "IdListFilter", + "type": "string" + }, + "fiscal_year": { + "filter_class": "NumberFilter", + "type": "number" + }, + "fiscal_year_gte": { + "filter_class": "NumberFilter", + "type": "number" + }, + "fiscal_year_lte": { + "filter_class": "NumberFilter", + "type": "number" + }, + "funding_agency": { + "filter_class": "PerformantAgencyFilter", + "type": "string" + }, + "funding_opportunity_number": { + "filter_class": "IdListFilter", + "type": "string" + }, + "key": { + "filter_class": "IdListFilter", + "type": "string" + }, + "obligated_gte": { + "filter_class": "NumberFilter", + "lookup": "gte", + "type": "number" + }, + "obligated_lte": { + "filter_class": "NumberFilter", + "lookup": "lte", + "type": "number" + }, + "ordering": { + "choices": [ + "-award_date", + "-obligated", + "-pragmatic_obligations", + "award_date", + "obligated", + "pragmatic_obligations" + ], + "choices_count": 6, + "filter_class": "OrderingFilter", + "type": "ordering" + }, + "pop_country": { + "filter_class": "BaseSmartFilter", + "type": "string" + }, + "pop_end_date_gte": { + "filter_class": "DateFilter", + "lookup": "gte", + "type": "date" + }, + "pop_end_date_lte": { + "filter_class": "DateFilter", + "lookup": "lte", + "type": "date" + }, + "pop_start_date_gte": { + "filter_class": "DateFilter", + "lookup": "gte", + "type": "date" + }, + "pop_start_date_lte": { + "filter_class": "DateFilter", + "lookup": "lte", + "type": "date" + }, + "pop_state": { + "filter_class": "BaseSmartFilter", + "type": "string" + }, + "pop_zip": { + "filter_class": "BaseSmartFilter", + "type": "string" + }, + "recipient": { + "filter_class": "CharFilter", + "type": "string" + }, + "record_type": { + "filter_class": "BaseSmartFilter", + "type": "string" + }, + "search": { + "filter_class": "CharFilter", + "type": "string" + }, + "uei": { + "filter_class": "UeiToEntityUuidFilter", + "type": "string" + }, + "uri": { + "filter_class": "IdListFilter", + "type": "string" + } + }, + "ordering_aliases": [], + "ordering_fields": [ + "award_date", + "obligated", + "pragmatic_obligations" + ], + "pagination": { + "class": "AssistanceKeysetPagination", + "max_page_size": 100 + }, + "shape": { + "expands": { + "assistance_listing": { + "expands": {}, + "fields": [ + "number", + "title" + ] + }, + "assistance_type": { + "expands": {}, + "fields": [ + "code", + "description" + ] + }, + "awarding_office": { + "expands": {}, + "fields": [ + "agency_code", + "agency_name", + "department_code", + "department_name", + "office_code", + "office_name", + "organization_id" + ] + }, + "funding_office": { + "expands": {}, + "fields": [ + "agency_code", + "agency_name", + "department_code", + "department_name", + "office_code", + "office_name", + "organization_id" + ] + }, + "listings": { + "expands": {}, + "fields": [ + "cfda_number", + "face_value_of_loan", + "first_action_date", + "indirect_cost_federal_share_amount", + "is_primary", + "last_action_date", + "loan_subsidy_cost", + "non_federal_funding_amount", + "obligated", + "pragmatic_obligations", + "transactions_count" + ] + }, + "obligations_by_fiscal_year": { + "expands": {}, + "fields": [ + "fiscal_year", + "obligated" + ] + }, + "officers": { + "expands": {}, + "fields": [ + "highly_compensated_officer_1_amount", + "highly_compensated_officer_1_name", + "highly_compensated_officer_2_amount", + "highly_compensated_officer_2_name", + "highly_compensated_officer_3_amount", + "highly_compensated_officer_3_name", + "highly_compensated_officer_4_amount", + "highly_compensated_officer_4_name", + "highly_compensated_officer_5_amount", + "highly_compensated_officer_5_name" + ] + }, + "period_of_performance": { + "expands": {}, + "fields": [ + "current_end_date", + "start_date" + ] + }, + "place_of_performance": { + "expands": {}, + "fields": [ + "city_name", + "code", + "country", + "county_fips", + "county_name", + "foreign_location", + "scope", + "state_code", + "state_fips", + "state_name", + "zip_code", + "zip_plus_four" + ] + }, + "recipient": { + "expands": {}, + "fields": [ + "business_types", + "congressional_district", + "congressional_district_original", + "county", + "duns", + "highest_owner", + "legal_business_name", + "legal_business_name_raw", + "uei", + "uuid" + ] + }, + "recipient_address": { + "expands": {}, + "fields": [ + "address_line1", + "address_line2", + "city", + "congressional_district_current", + "congressional_district_original", + "country_code", + "county_fips", + "foreign_city_name", + "foreign_postal_code", + "foreign_province_name", + "state_fips", + "state_or_province_code", + "zip_code", + "zip_code_plus4" + ] + }, + "transactions": { + "expands": {}, + "fields": [ + "action_date_fiscal_year", + "action_type", + "assistance_type_code", + "award_amount_basis", + "award_recipient_basis", + "cfda_number", + "correction_delete_indicator", + "face_value_of_loan", + "indirect_cost_federal_share_amount", + "initial_report_date", + "key", + "last_modified_date", + "loan_subsidy_cost", + "modification_number", + "non_federal_funding_amount", + "obligated", + "pragmatic_obligations", + "transaction_date" + ] + } + }, + "fields": [ + "assistance_type", + "assistance_types_count", + "award_amount_basis", + "award_date", + "award_recipient_basis", + "award_total_face_value_of_loan", + "award_total_loan_subsidy_cost", + "award_total_non_federal_funding", + "award_total_obligated", + "award_total_outlayed", + "business_funds_indicator", + "cfda_number", + "covid19_obligated", + "covid19_outlayed", + "disaster_emergency_fund_codes", + "face_value_of_loan", + "fain", + "federal_accounts_funding_this_award", + "fiscal_year", + "former_award_keys", + "funding_opportunity_goals_text", + "funding_opportunity_number", + "iija_obligated", + "iija_outlayed", + "indirect_cost_federal_share_amount", + "insurance_coverage_amount", + "key", + "last_action_date", + "last_modified_date", + "listings_count", + "loan_subsidy_cost", + "non_federal_funding_amount", + "object_classes_funding_this_award", + "obligated", + "pragmatic_obligations", + "program_activities_funding_this_award", + "purposes_differ", + "record_type", + "sai_number", + "summary", + "title", + "transactions", + "transactions_count", + "treasury_accounts_funding_this_award", + "uri" + ] + }, + "shape_flat_paths": [ + "assistance_listing", + "assistance_listing.number", + "assistance_listing.title", + "assistance_type", + "assistance_type.code", + "assistance_type.description", + "assistance_types_count", + "award_amount_basis", + "award_date", + "award_recipient_basis", + "award_total_face_value_of_loan", + "award_total_loan_subsidy_cost", + "award_total_non_federal_funding", + "award_total_obligated", + "award_total_outlayed", + "awarding_office", + "awarding_office.agency_code", + "awarding_office.agency_name", + "awarding_office.department_code", + "awarding_office.department_name", + "awarding_office.office_code", + "awarding_office.office_name", + "awarding_office.organization_id", + "business_funds_indicator", + "cfda_number", + "covid19_obligated", + "covid19_outlayed", + "disaster_emergency_fund_codes", + "face_value_of_loan", + "fain", + "federal_accounts_funding_this_award", + "fiscal_year", + "former_award_keys", + "funding_office", + "funding_office.agency_code", + "funding_office.agency_name", + "funding_office.department_code", + "funding_office.department_name", + "funding_office.office_code", + "funding_office.office_name", + "funding_office.organization_id", + "funding_opportunity_goals_text", + "funding_opportunity_number", + "iija_obligated", + "iija_outlayed", + "indirect_cost_federal_share_amount", + "insurance_coverage_amount", + "key", + "last_action_date", + "last_modified_date", + "listings", + "listings.cfda_number", + "listings.face_value_of_loan", + "listings.first_action_date", + "listings.indirect_cost_federal_share_amount", + "listings.is_primary", + "listings.last_action_date", + "listings.loan_subsidy_cost", + "listings.non_federal_funding_amount", + "listings.obligated", + "listings.pragmatic_obligations", + "listings.transactions_count", + "listings_count", + "loan_subsidy_cost", + "non_federal_funding_amount", + "object_classes_funding_this_award", + "obligated", + "obligations_by_fiscal_year", + "obligations_by_fiscal_year.fiscal_year", + "obligations_by_fiscal_year.obligated", + "officers", + "officers.highly_compensated_officer_1_amount", + "officers.highly_compensated_officer_1_name", + "officers.highly_compensated_officer_2_amount", + "officers.highly_compensated_officer_2_name", + "officers.highly_compensated_officer_3_amount", + "officers.highly_compensated_officer_3_name", + "officers.highly_compensated_officer_4_amount", + "officers.highly_compensated_officer_4_name", + "officers.highly_compensated_officer_5_amount", + "officers.highly_compensated_officer_5_name", + "period_of_performance", + "period_of_performance.current_end_date", + "period_of_performance.start_date", + "place_of_performance", + "place_of_performance.city_name", + "place_of_performance.code", + "place_of_performance.country", + "place_of_performance.county_fips", + "place_of_performance.county_name", + "place_of_performance.foreign_location", + "place_of_performance.scope", + "place_of_performance.state_code", + "place_of_performance.state_fips", + "place_of_performance.state_name", + "place_of_performance.zip_code", + "place_of_performance.zip_plus_four", + "pragmatic_obligations", + "program_activities_funding_this_award", + "purposes_differ", + "recipient", + "recipient.business_types", + "recipient.congressional_district", + "recipient.congressional_district_original", + "recipient.county", + "recipient.duns", + "recipient.highest_owner", + "recipient.legal_business_name", + "recipient.legal_business_name_raw", + "recipient.uei", + "recipient.uuid", + "recipient_address", + "recipient_address.address_line1", + "recipient_address.address_line2", + "recipient_address.city", + "recipient_address.congressional_district_current", + "recipient_address.congressional_district_original", + "recipient_address.country_code", + "recipient_address.county_fips", + "recipient_address.foreign_city_name", + "recipient_address.foreign_postal_code", + "recipient_address.foreign_province_name", + "recipient_address.state_fips", + "recipient_address.state_or_province_code", + "recipient_address.zip_code", + "recipient_address.zip_code_plus4", + "record_type", + "sai_number", + "summary", + "title", + "transactions", + "transactions.action_date_fiscal_year", + "transactions.action_type", + "transactions.assistance_type_code", + "transactions.award_amount_basis", + "transactions.award_recipient_basis", + "transactions.cfda_number", + "transactions.correction_delete_indicator", + "transactions.face_value_of_loan", + "transactions.indirect_cost_federal_share_amount", + "transactions.initial_report_date", + "transactions.key", + "transactions.last_modified_date", + "transactions.loan_subsidy_cost", + "transactions.modification_number", + "transactions.non_federal_funding_amount", + "transactions.obligated", + "transactions.pragmatic_obligations", + "transactions.transaction_date", + "transactions_count", + "treasury_accounts_funding_this_award", + "uri" + ], + "shape_gated_by_level": {}, + "shape_supported": true, + "shape_tier_required": null, + "viewset": "assistance.views.GrantAwardViewSet" + }, + "swagger_has_key": false, + "swagger_params": [] + }, + "assistance/loans": { + "basename": "assistance-loans", + "docs_file": null, + "docs_params": [], + "prefix": "assistance/loans", + "resource_key": "assistance/loans", + "runtime": { + "filter_params": [ + "assistance_type", + "award_date", + "award_date_gte", + "award_date_lte", + "awarding_agency", + "business_funds_indicator", + "cfda_number", + "disaster_emergency_fund_code", + "fain", + "fiscal_year", + "fiscal_year_gte", + "fiscal_year_lte", + "funding_agency", + "funding_opportunity_number", + "key", + "obligated_gte", + "obligated_lte", + "ordering", + "pop_country", + "pop_end_date_gte", + "pop_end_date_lte", + "pop_start_date_gte", + "pop_start_date_lte", + "pop_state", + "pop_zip", + "recipient", + "record_type", + "search", + "uei", + "uri" + ], + "filter_params_detail": { + "assistance_type": { + "filter_class": "BaseSmartFilter", + "type": "string" + }, + "award_date": { + "filter_class": "DateFilter", + "type": "date" + }, + "award_date_gte": { + "filter_class": "DateFilter", + "type": "date" + }, + "award_date_lte": { + "filter_class": "DateFilter", + "type": "date" + }, + "awarding_agency": { + "filter_class": "PerformantAgencyFilter", + "type": "string" + }, + "business_funds_indicator": { + "filter_class": "BaseSmartFilter", + "type": "string" + }, + "cfda_number": { + "filter_class": "IdListFilter", + "type": "string" + }, + "disaster_emergency_fund_code": { + "filter_class": "BaseSmartFilter", + "type": "string" + }, + "fain": { + "filter_class": "IdListFilter", + "type": "string" + }, + "fiscal_year": { + "filter_class": "NumberFilter", + "type": "number" + }, + "fiscal_year_gte": { + "filter_class": "NumberFilter", + "type": "number" + }, + "fiscal_year_lte": { + "filter_class": "NumberFilter", + "type": "number" + }, + "funding_agency": { + "filter_class": "PerformantAgencyFilter", + "type": "string" + }, + "funding_opportunity_number": { + "filter_class": "IdListFilter", + "type": "string" + }, + "key": { + "filter_class": "IdListFilter", + "type": "string" + }, + "obligated_gte": { + "filter_class": "NumberFilter", + "lookup": "gte", + "type": "number" + }, + "obligated_lte": { + "filter_class": "NumberFilter", + "lookup": "lte", + "type": "number" + }, + "ordering": { + "choices": [ + "-award_date", + "-obligated", + "-pragmatic_obligations", + "award_date", + "obligated", + "pragmatic_obligations" + ], + "choices_count": 6, + "filter_class": "OrderingFilter", + "type": "ordering" + }, + "pop_country": { + "filter_class": "BaseSmartFilter", + "type": "string" + }, + "pop_end_date_gte": { + "filter_class": "DateFilter", + "lookup": "gte", + "type": "date" + }, + "pop_end_date_lte": { + "filter_class": "DateFilter", + "lookup": "lte", + "type": "date" + }, + "pop_start_date_gte": { + "filter_class": "DateFilter", + "lookup": "gte", + "type": "date" + }, + "pop_start_date_lte": { + "filter_class": "DateFilter", + "lookup": "lte", + "type": "date" + }, + "pop_state": { + "filter_class": "BaseSmartFilter", + "type": "string" + }, + "pop_zip": { + "filter_class": "BaseSmartFilter", + "type": "string" + }, + "recipient": { + "filter_class": "CharFilter", + "type": "string" + }, + "record_type": { + "filter_class": "BaseSmartFilter", + "type": "string" + }, + "search": { + "filter_class": "CharFilter", + "type": "string" + }, + "uei": { + "filter_class": "UeiToEntityUuidFilter", + "type": "string" + }, + "uri": { + "filter_class": "IdListFilter", + "type": "string" + } + }, + "ordering_aliases": [], + "ordering_fields": [ + "award_date", + "obligated", + "pragmatic_obligations" + ], + "pagination": { + "class": "AssistanceKeysetPagination", + "max_page_size": 100 + }, + "shape": { + "expands": { + "assistance_listing": { + "expands": {}, + "fields": [ + "number", + "title" + ] + }, + "assistance_type": { + "expands": {}, + "fields": [ + "code", + "description" + ] + }, + "awarding_office": { + "expands": {}, + "fields": [ + "agency_code", + "agency_name", + "department_code", + "department_name", + "office_code", + "office_name", + "organization_id" + ] + }, + "funding_office": { + "expands": {}, + "fields": [ + "agency_code", + "agency_name", + "department_code", + "department_name", + "office_code", + "office_name", + "organization_id" + ] + }, + "listings": { + "expands": {}, + "fields": [ + "cfda_number", + "face_value_of_loan", + "first_action_date", + "indirect_cost_federal_share_amount", + "is_primary", + "last_action_date", + "loan_subsidy_cost", + "non_federal_funding_amount", + "obligated", + "pragmatic_obligations", + "transactions_count" + ] + }, + "obligations_by_fiscal_year": { + "expands": {}, + "fields": [ + "fiscal_year", + "obligated" + ] + }, + "officers": { + "expands": {}, + "fields": [ + "highly_compensated_officer_1_amount", + "highly_compensated_officer_1_name", + "highly_compensated_officer_2_amount", + "highly_compensated_officer_2_name", + "highly_compensated_officer_3_amount", + "highly_compensated_officer_3_name", + "highly_compensated_officer_4_amount", + "highly_compensated_officer_4_name", + "highly_compensated_officer_5_amount", + "highly_compensated_officer_5_name" + ] + }, + "period_of_performance": { + "expands": {}, + "fields": [ + "current_end_date", + "start_date" + ] + }, + "place_of_performance": { + "expands": {}, + "fields": [ + "city_name", + "code", + "country", + "county_fips", + "county_name", + "foreign_location", + "scope", + "state_code", + "state_fips", + "state_name", + "zip_code", + "zip_plus_four" + ] + }, + "recipient": { + "expands": {}, + "fields": [ + "business_types", + "congressional_district", + "congressional_district_original", + "county", + "duns", + "highest_owner", + "legal_business_name", + "legal_business_name_raw", + "uei", + "uuid" + ] + }, + "recipient_address": { + "expands": {}, + "fields": [ + "address_line1", + "address_line2", + "city", + "congressional_district_current", + "congressional_district_original", + "country_code", + "county_fips", + "foreign_city_name", + "foreign_postal_code", + "foreign_province_name", + "state_fips", + "state_or_province_code", + "zip_code", + "zip_code_plus4" + ] + }, + "transactions": { + "expands": {}, + "fields": [ + "action_date_fiscal_year", + "action_type", + "assistance_type_code", + "award_amount_basis", + "award_recipient_basis", + "cfda_number", + "correction_delete_indicator", + "face_value_of_loan", + "indirect_cost_federal_share_amount", + "initial_report_date", + "key", + "last_modified_date", + "loan_subsidy_cost", + "modification_number", + "non_federal_funding_amount", + "obligated", + "pragmatic_obligations", + "transaction_date" + ] + } + }, + "fields": [ + "assistance_type", + "assistance_types_count", + "award_amount_basis", + "award_date", + "award_recipient_basis", + "award_total_face_value_of_loan", + "award_total_loan_subsidy_cost", + "award_total_non_federal_funding", + "award_total_obligated", + "award_total_outlayed", + "business_funds_indicator", + "cfda_number", + "covid19_obligated", + "covid19_outlayed", + "disaster_emergency_fund_codes", + "face_value_of_loan", + "fain", + "federal_accounts_funding_this_award", + "fiscal_year", + "former_award_keys", + "funding_opportunity_goals_text", + "funding_opportunity_number", + "iija_obligated", + "iija_outlayed", + "indirect_cost_federal_share_amount", + "insurance_coverage_amount", + "key", + "last_action_date", + "last_modified_date", + "listings_count", + "loan_subsidy_cost", + "non_federal_funding_amount", + "object_classes_funding_this_award", + "obligated", + "pragmatic_obligations", + "program_activities_funding_this_award", + "purposes_differ", + "record_type", + "sai_number", + "summary", + "title", + "transactions", + "transactions_count", + "treasury_accounts_funding_this_award", + "uri" + ] + }, + "shape_flat_paths": [ + "assistance_listing", + "assistance_listing.number", + "assistance_listing.title", + "assistance_type", + "assistance_type.code", + "assistance_type.description", + "assistance_types_count", + "award_amount_basis", + "award_date", + "award_recipient_basis", + "award_total_face_value_of_loan", + "award_total_loan_subsidy_cost", + "award_total_non_federal_funding", + "award_total_obligated", + "award_total_outlayed", + "awarding_office", + "awarding_office.agency_code", + "awarding_office.agency_name", + "awarding_office.department_code", + "awarding_office.department_name", + "awarding_office.office_code", + "awarding_office.office_name", + "awarding_office.organization_id", + "business_funds_indicator", + "cfda_number", + "covid19_obligated", + "covid19_outlayed", + "disaster_emergency_fund_codes", + "face_value_of_loan", + "fain", + "federal_accounts_funding_this_award", + "fiscal_year", + "former_award_keys", + "funding_office", + "funding_office.agency_code", + "funding_office.agency_name", + "funding_office.department_code", + "funding_office.department_name", + "funding_office.office_code", + "funding_office.office_name", + "funding_office.organization_id", + "funding_opportunity_goals_text", + "funding_opportunity_number", + "iija_obligated", + "iija_outlayed", + "indirect_cost_federal_share_amount", + "insurance_coverage_amount", + "key", + "last_action_date", + "last_modified_date", + "listings", + "listings.cfda_number", + "listings.face_value_of_loan", + "listings.first_action_date", + "listings.indirect_cost_federal_share_amount", + "listings.is_primary", + "listings.last_action_date", + "listings.loan_subsidy_cost", + "listings.non_federal_funding_amount", + "listings.obligated", + "listings.pragmatic_obligations", + "listings.transactions_count", + "listings_count", + "loan_subsidy_cost", + "non_federal_funding_amount", + "object_classes_funding_this_award", + "obligated", + "obligations_by_fiscal_year", + "obligations_by_fiscal_year.fiscal_year", + "obligations_by_fiscal_year.obligated", + "officers", + "officers.highly_compensated_officer_1_amount", + "officers.highly_compensated_officer_1_name", + "officers.highly_compensated_officer_2_amount", + "officers.highly_compensated_officer_2_name", + "officers.highly_compensated_officer_3_amount", + "officers.highly_compensated_officer_3_name", + "officers.highly_compensated_officer_4_amount", + "officers.highly_compensated_officer_4_name", + "officers.highly_compensated_officer_5_amount", + "officers.highly_compensated_officer_5_name", + "period_of_performance", + "period_of_performance.current_end_date", + "period_of_performance.start_date", + "place_of_performance", + "place_of_performance.city_name", + "place_of_performance.code", + "place_of_performance.country", + "place_of_performance.county_fips", + "place_of_performance.county_name", + "place_of_performance.foreign_location", + "place_of_performance.scope", + "place_of_performance.state_code", + "place_of_performance.state_fips", + "place_of_performance.state_name", + "place_of_performance.zip_code", + "place_of_performance.zip_plus_four", + "pragmatic_obligations", + "program_activities_funding_this_award", + "purposes_differ", + "recipient", + "recipient.business_types", + "recipient.congressional_district", + "recipient.congressional_district_original", + "recipient.county", + "recipient.duns", + "recipient.highest_owner", + "recipient.legal_business_name", + "recipient.legal_business_name_raw", + "recipient.uei", + "recipient.uuid", + "recipient_address", + "recipient_address.address_line1", + "recipient_address.address_line2", + "recipient_address.city", + "recipient_address.congressional_district_current", + "recipient_address.congressional_district_original", + "recipient_address.country_code", + "recipient_address.county_fips", + "recipient_address.foreign_city_name", + "recipient_address.foreign_postal_code", + "recipient_address.foreign_province_name", + "recipient_address.state_fips", + "recipient_address.state_or_province_code", + "recipient_address.zip_code", + "recipient_address.zip_code_plus4", + "record_type", + "sai_number", + "summary", + "title", + "transactions", + "transactions.action_date_fiscal_year", + "transactions.action_type", + "transactions.assistance_type_code", + "transactions.award_amount_basis", + "transactions.award_recipient_basis", + "transactions.cfda_number", + "transactions.correction_delete_indicator", + "transactions.face_value_of_loan", + "transactions.indirect_cost_federal_share_amount", + "transactions.initial_report_date", + "transactions.key", + "transactions.last_modified_date", + "transactions.loan_subsidy_cost", + "transactions.modification_number", + "transactions.non_federal_funding_amount", + "transactions.obligated", + "transactions.pragmatic_obligations", + "transactions.transaction_date", + "transactions_count", + "treasury_accounts_funding_this_award", + "uri" + ], + "shape_gated_by_level": {}, + "shape_supported": true, + "shape_tier_required": null, + "viewset": "assistance.views.LoanAwardViewSet" + }, + "swagger_has_key": false, + "swagger_params": [] + }, + "assistance/other": { + "basename": "assistance-other", + "docs_file": null, + "docs_params": [], + "prefix": "assistance/other", + "resource_key": "assistance/other", + "runtime": { + "filter_params": [ + "assistance_type", + "award_date", + "award_date_gte", + "award_date_lte", + "awarding_agency", + "business_funds_indicator", + "cfda_number", + "disaster_emergency_fund_code", + "fain", + "fiscal_year", + "fiscal_year_gte", + "fiscal_year_lte", + "funding_agency", + "funding_opportunity_number", + "key", + "obligated_gte", + "obligated_lte", + "ordering", + "pop_country", + "pop_end_date_gte", + "pop_end_date_lte", + "pop_start_date_gte", + "pop_start_date_lte", + "pop_state", + "pop_zip", + "recipient", + "record_type", + "search", + "uei", + "uri" + ], + "filter_params_detail": { + "assistance_type": { + "filter_class": "BaseSmartFilter", + "type": "string" + }, + "award_date": { + "filter_class": "DateFilter", + "type": "date" + }, + "award_date_gte": { + "filter_class": "DateFilter", + "type": "date" + }, + "award_date_lte": { + "filter_class": "DateFilter", + "type": "date" + }, + "awarding_agency": { + "filter_class": "PerformantAgencyFilter", + "type": "string" + }, + "business_funds_indicator": { + "filter_class": "BaseSmartFilter", + "type": "string" + }, + "cfda_number": { + "filter_class": "IdListFilter", + "type": "string" + }, + "disaster_emergency_fund_code": { + "filter_class": "BaseSmartFilter", + "type": "string" + }, + "fain": { + "filter_class": "IdListFilter", + "type": "string" + }, + "fiscal_year": { + "filter_class": "NumberFilter", + "type": "number" + }, + "fiscal_year_gte": { + "filter_class": "NumberFilter", + "type": "number" + }, + "fiscal_year_lte": { + "filter_class": "NumberFilter", + "type": "number" + }, + "funding_agency": { + "filter_class": "PerformantAgencyFilter", + "type": "string" + }, + "funding_opportunity_number": { + "filter_class": "IdListFilter", + "type": "string" + }, + "key": { + "filter_class": "IdListFilter", + "type": "string" + }, + "obligated_gte": { + "filter_class": "NumberFilter", + "lookup": "gte", + "type": "number" + }, + "obligated_lte": { + "filter_class": "NumberFilter", + "lookup": "lte", + "type": "number" + }, + "ordering": { + "choices": [ + "-award_date", + "-obligated", + "-pragmatic_obligations", + "award_date", + "obligated", + "pragmatic_obligations" + ], + "choices_count": 6, + "filter_class": "OrderingFilter", + "type": "ordering" + }, + "pop_country": { + "filter_class": "BaseSmartFilter", + "type": "string" + }, + "pop_end_date_gte": { + "filter_class": "DateFilter", + "lookup": "gte", + "type": "date" + }, + "pop_end_date_lte": { + "filter_class": "DateFilter", + "lookup": "lte", + "type": "date" + }, + "pop_start_date_gte": { + "filter_class": "DateFilter", + "lookup": "gte", + "type": "date" + }, + "pop_start_date_lte": { + "filter_class": "DateFilter", + "lookup": "lte", + "type": "date" + }, + "pop_state": { + "filter_class": "BaseSmartFilter", + "type": "string" + }, + "pop_zip": { + "filter_class": "BaseSmartFilter", + "type": "string" + }, + "recipient": { + "filter_class": "CharFilter", + "type": "string" + }, + "record_type": { + "filter_class": "BaseSmartFilter", + "type": "string" + }, + "search": { + "filter_class": "CharFilter", + "type": "string" + }, + "uei": { + "filter_class": "UeiToEntityUuidFilter", + "type": "string" + }, + "uri": { + "filter_class": "IdListFilter", + "type": "string" + } + }, + "ordering_aliases": [], + "ordering_fields": [ + "award_date", + "obligated", + "pragmatic_obligations" + ], + "pagination": { + "class": "AssistanceKeysetPagination", + "max_page_size": 100 + }, + "shape": { + "expands": { + "assistance_listing": { + "expands": {}, + "fields": [ + "number", + "title" + ] + }, + "assistance_type": { + "expands": {}, + "fields": [ + "code", + "description" + ] + }, + "awarding_office": { + "expands": {}, + "fields": [ + "agency_code", + "agency_name", + "department_code", + "department_name", + "office_code", + "office_name", + "organization_id" + ] + }, + "funding_office": { + "expands": {}, + "fields": [ + "agency_code", + "agency_name", + "department_code", + "department_name", + "office_code", + "office_name", + "organization_id" + ] + }, + "listings": { + "expands": {}, + "fields": [ + "cfda_number", + "face_value_of_loan", + "first_action_date", + "indirect_cost_federal_share_amount", + "is_primary", + "last_action_date", + "loan_subsidy_cost", + "non_federal_funding_amount", + "obligated", + "pragmatic_obligations", + "transactions_count" + ] + }, + "obligations_by_fiscal_year": { + "expands": {}, + "fields": [ + "fiscal_year", + "obligated" + ] + }, + "officers": { + "expands": {}, + "fields": [ + "highly_compensated_officer_1_amount", + "highly_compensated_officer_1_name", + "highly_compensated_officer_2_amount", + "highly_compensated_officer_2_name", + "highly_compensated_officer_3_amount", + "highly_compensated_officer_3_name", + "highly_compensated_officer_4_amount", + "highly_compensated_officer_4_name", + "highly_compensated_officer_5_amount", + "highly_compensated_officer_5_name" + ] + }, + "period_of_performance": { + "expands": {}, + "fields": [ + "current_end_date", + "start_date" + ] + }, + "place_of_performance": { + "expands": {}, + "fields": [ + "city_name", + "code", + "country", + "county_fips", + "county_name", + "foreign_location", + "scope", + "state_code", + "state_fips", + "state_name", + "zip_code", + "zip_plus_four" + ] + }, + "recipient": { + "expands": {}, + "fields": [ + "business_types", + "congressional_district", + "congressional_district_original", + "county", + "duns", + "highest_owner", + "legal_business_name", + "legal_business_name_raw", + "uei", + "uuid" + ] + }, + "recipient_address": { + "expands": {}, + "fields": [ + "address_line1", + "address_line2", + "city", + "congressional_district_current", + "congressional_district_original", + "country_code", + "county_fips", + "foreign_city_name", + "foreign_postal_code", + "foreign_province_name", + "state_fips", + "state_or_province_code", + "zip_code", + "zip_code_plus4" + ] + }, + "transactions": { + "expands": {}, + "fields": [ + "action_date_fiscal_year", + "action_type", + "assistance_type_code", + "award_amount_basis", + "award_recipient_basis", + "cfda_number", + "correction_delete_indicator", + "face_value_of_loan", + "indirect_cost_federal_share_amount", + "initial_report_date", + "key", + "last_modified_date", + "loan_subsidy_cost", + "modification_number", + "non_federal_funding_amount", + "obligated", + "pragmatic_obligations", + "transaction_date" + ] + } + }, + "fields": [ + "assistance_type", + "assistance_types_count", + "award_amount_basis", + "award_date", + "award_recipient_basis", + "award_total_face_value_of_loan", + "award_total_loan_subsidy_cost", + "award_total_non_federal_funding", + "award_total_obligated", + "award_total_outlayed", + "business_funds_indicator", + "cfda_number", + "covid19_obligated", + "covid19_outlayed", + "disaster_emergency_fund_codes", + "face_value_of_loan", + "fain", + "federal_accounts_funding_this_award", + "fiscal_year", + "former_award_keys", + "funding_opportunity_goals_text", + "funding_opportunity_number", + "iija_obligated", + "iija_outlayed", + "indirect_cost_federal_share_amount", + "insurance_coverage_amount", + "key", + "last_action_date", + "last_modified_date", + "listings_count", + "loan_subsidy_cost", + "non_federal_funding_amount", + "object_classes_funding_this_award", + "obligated", + "pragmatic_obligations", + "program_activities_funding_this_award", + "purposes_differ", + "record_type", + "sai_number", + "summary", + "title", + "transactions", + "transactions_count", + "treasury_accounts_funding_this_award", + "uri" + ] + }, + "shape_flat_paths": [ + "assistance_listing", + "assistance_listing.number", + "assistance_listing.title", + "assistance_type", + "assistance_type.code", + "assistance_type.description", + "assistance_types_count", + "award_amount_basis", + "award_date", + "award_recipient_basis", + "award_total_face_value_of_loan", + "award_total_loan_subsidy_cost", + "award_total_non_federal_funding", + "award_total_obligated", + "award_total_outlayed", + "awarding_office", + "awarding_office.agency_code", + "awarding_office.agency_name", + "awarding_office.department_code", + "awarding_office.department_name", + "awarding_office.office_code", + "awarding_office.office_name", + "awarding_office.organization_id", + "business_funds_indicator", + "cfda_number", + "covid19_obligated", + "covid19_outlayed", + "disaster_emergency_fund_codes", + "face_value_of_loan", + "fain", + "federal_accounts_funding_this_award", + "fiscal_year", + "former_award_keys", + "funding_office", + "funding_office.agency_code", + "funding_office.agency_name", + "funding_office.department_code", + "funding_office.department_name", + "funding_office.office_code", + "funding_office.office_name", + "funding_office.organization_id", + "funding_opportunity_goals_text", + "funding_opportunity_number", + "iija_obligated", + "iija_outlayed", + "indirect_cost_federal_share_amount", + "insurance_coverage_amount", + "key", + "last_action_date", + "last_modified_date", + "listings", + "listings.cfda_number", + "listings.face_value_of_loan", + "listings.first_action_date", + "listings.indirect_cost_federal_share_amount", + "listings.is_primary", + "listings.last_action_date", + "listings.loan_subsidy_cost", + "listings.non_federal_funding_amount", + "listings.obligated", + "listings.pragmatic_obligations", + "listings.transactions_count", + "listings_count", + "loan_subsidy_cost", + "non_federal_funding_amount", + "object_classes_funding_this_award", + "obligated", + "obligations_by_fiscal_year", + "obligations_by_fiscal_year.fiscal_year", + "obligations_by_fiscal_year.obligated", + "officers", + "officers.highly_compensated_officer_1_amount", + "officers.highly_compensated_officer_1_name", + "officers.highly_compensated_officer_2_amount", + "officers.highly_compensated_officer_2_name", + "officers.highly_compensated_officer_3_amount", + "officers.highly_compensated_officer_3_name", + "officers.highly_compensated_officer_4_amount", + "officers.highly_compensated_officer_4_name", + "officers.highly_compensated_officer_5_amount", + "officers.highly_compensated_officer_5_name", + "period_of_performance", + "period_of_performance.current_end_date", + "period_of_performance.start_date", + "place_of_performance", + "place_of_performance.city_name", + "place_of_performance.code", + "place_of_performance.country", + "place_of_performance.county_fips", + "place_of_performance.county_name", + "place_of_performance.foreign_location", + "place_of_performance.scope", + "place_of_performance.state_code", + "place_of_performance.state_fips", + "place_of_performance.state_name", + "place_of_performance.zip_code", + "place_of_performance.zip_plus_four", + "pragmatic_obligations", + "program_activities_funding_this_award", + "purposes_differ", + "recipient", + "recipient.business_types", + "recipient.congressional_district", + "recipient.congressional_district_original", + "recipient.county", + "recipient.duns", + "recipient.highest_owner", + "recipient.legal_business_name", + "recipient.legal_business_name_raw", + "recipient.uei", + "recipient.uuid", + "recipient_address", + "recipient_address.address_line1", + "recipient_address.address_line2", + "recipient_address.city", + "recipient_address.congressional_district_current", + "recipient_address.congressional_district_original", + "recipient_address.country_code", + "recipient_address.county_fips", + "recipient_address.foreign_city_name", + "recipient_address.foreign_postal_code", + "recipient_address.foreign_province_name", + "recipient_address.state_fips", + "recipient_address.state_or_province_code", + "recipient_address.zip_code", + "recipient_address.zip_code_plus4", + "record_type", + "sai_number", + "summary", + "title", + "transactions", + "transactions.action_date_fiscal_year", + "transactions.action_type", + "transactions.assistance_type_code", + "transactions.award_amount_basis", + "transactions.award_recipient_basis", + "transactions.cfda_number", + "transactions.correction_delete_indicator", + "transactions.face_value_of_loan", + "transactions.indirect_cost_federal_share_amount", + "transactions.initial_report_date", + "transactions.key", + "transactions.last_modified_date", + "transactions.loan_subsidy_cost", + "transactions.modification_number", + "transactions.non_federal_funding_amount", + "transactions.obligated", + "transactions.pragmatic_obligations", + "transactions.transaction_date", + "transactions_count", + "treasury_accounts_funding_this_award", + "uri" + ], + "shape_gated_by_level": {}, + "shape_supported": true, + "shape_tier_required": null, + "viewset": "assistance.views.OtherAssistanceAwardViewSet" + }, + "swagger_has_key": false, + "swagger_params": [] + }, "assistance_listings": { "basename": "assistancelisting", "docs_file": null, @@ -17665,6 +19889,10 @@ "agencies", "agencies_contracts_awarding", "agencies_contracts_funding", + "assistance/direct_payments", + "assistance/grants", + "assistance/loans", + "assistance/other", "assistance_listings", "budget/accounts", "business_types", @@ -17717,6 +19945,10 @@ "no_swagger": [ "agencies_contracts_awarding", "agencies_contracts_funding", + "assistance/direct_payments", + "assistance/grants", + "assistance/loans", + "assistance/other", "budget/accounts", "contracts_subawards", "dibbs/awards", diff --git a/docs/API_REFERENCE.md b/docs/API_REFERENCE.md index deed046..35c5f9d 100644 --- a/docs/API_REFERENCE.md +++ b/docs/API_REFERENCE.md @@ -416,6 +416,91 @@ const subs = await client.listSubawards({ prime_uei: "ABC123DEF456", limit: 25 } --- +## Financial-Assistance Awards + +Federal financial-assistance awards from USAspending, in four groups by GSDM assistance type. Requires Tango API 5.12.0. + +| Group | Assistance types | List | Detail | Transactions | Listings | +| ---------------------------------- | ----------------------------------------------------------------------------- | -------------------------------- | --------------------------------- | ---------------------------------------------- | ------------------------------------------ | +| `/api/assistance/grants/` | `F001` grant, `F002` cooperative agreement | `listAssistanceGrants()` | `getAssistanceGrant(key)` | `listAssistanceGrantTransactions(key)` | `listAssistanceGrantListings(key)` | +| `/api/assistance/loans/` | `F003` direct loan, `F004` loan guarantee | `listAssistanceLoans()` | `getAssistanceLoan(key)` | `listAssistanceLoanTransactions(key)` | `listAssistanceLoanListings(key)` | +| `/api/assistance/direct_payments/` | `F006` direct payment for specified use, `F007` with unrestricted use | `listAssistanceDirectPayments()` | `getAssistanceDirectPayment(key)` | `listAssistanceDirectPaymentTransactions(key)` | `listAssistanceDirectPaymentListings(key)` | +| `/api/assistance/other/` | `F005` insurance, `F008` asset forfeiture, `F009` property sale, `F010` other | `listAssistanceOther()` | `getAssistanceOther(key)` | `listAssistanceOtherTransactions(key)` | `listAssistanceOtherListings(key)` | + +All four groups take the same filters and return the same shape. These are awards already obligated; grant opportunities you can apply for are `listGrants()`. + +### `listAssistanceGrants(options?)` (and the other three lists) + +```ts +const grants = await client.listAssistanceGrants({ + cfda_number: "93.778", + pop_state: "VA", + fiscal_year: 2026, + ordering: "-obligated", + limit: 25, +}); + +for (const award of grants.results) { + console.log(award.key, award.award_date, award.obligated, (award.recipient as { legal_business_name?: string } | null)?.legal_business_name); +} + +const next = await client.listAssistanceGrants({ cfda_number: "93.778", cursor: grants.cursor }); +``` + +The lists are cursor-paginated: pass the previous page's `cursor`, or walk them with `iterateAssistanceGrants()` / `iterateAssistanceLoans()` / `iterateAssistanceDirectPayments()` / `iterateAssistanceOther()`. `page` is not accepted. `limit` caps at 100. Every list defaults to `ShapeConfig.ASSISTANCE_AWARDS_MINIMAL`, the API's own default shape. + +#### Parameters (financial-assistance awards) + +| Name | Type | Description | +| ------------------------------------------ | ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------- | +| `key` | `string` | Award key, exact. OR several with `\|`. | +| `fain` | `string` | Federal Award Identification Number, exact. OR several with `\|`. | +| `uri` | `string` | Unique Record Identifier, carried by aggregate records, exact. OR several with `\|`. | +| `funding_opportunity_number` | `string` | The funding opportunity number the award was made under, as on the Grants.gov opportunity. OR several with `\|`. | +| `uei` | `string` | Recipient UEI, exact. OR several with `\|`. | +| `recipient` | `string` | Recipient name or UEI; each term matches the recipients closest to it by name. A term under three characters returns 400. OR several with `\|`. | +| `awarding_agency` / `funding_agency` | `string` | Agency code, name or abbreviation (best-effort matching). OR several with `\|`. | +| `cfda_number` | `string` | The award's primary assistance listing (CFDA) number, e.g. `93.778`. OR several with `\|`. | +| `assistance_type` | `string` | GSDM assistance type code (`F001`-`F010`), case-insensitive. OR several with `\|`. | +| `record_type` | `string` | `1` aggregate, `2` non-aggregate, `3` individual recipient (PII-redacted). OR several with `\|`. | +| `business_funds_indicator` | `string` | `NON` (not Recovery Act) or `REC` (Recovery Act), case-insensitive. | +| `disaster_emergency_fund_code` | `string` | Disaster Emergency Fund Code, e.g. `L`, case-insensitive. OR several with `\|`. | +| `award_date` / `award_date_gte` / `_lte` | `string` | Award date, exact or range (`YYYY-MM-DD`). | +| `fiscal_year` / `fiscal_year_gte` / `_lte` | `number \| string` | Federal fiscal year of the award, exact or range. | +| `pop_start_date_gte` / `_lte` | `string` | Period of performance start date range (`YYYY-MM-DD`). | +| `pop_end_date_gte` / `_lte` | `string` | Period of performance current end date range (`YYYY-MM-DD`). | +| `pop_state` / `pop_country` / `pop_zip` | `string` | Place of performance state code, ISO 3166 alpha-3 country code, or five-digit ZIP. OR several with `\|`. | +| `obligated_gte` / `obligated_lte` | `number \| string` | Obligated amount range (USD). | +| `search` | `string` | Full-text search over identifiers, recipient names, assistance listing titles and award descriptions. Web-search syntax: `a b`, `a or b`, `a -b`. | +| `ordering` | `string` | `award_date` (default `-award_date`), `obligated` or `pragmatic_obligations`. | + +To compare dollar amounts across the four groups, read or order by `pragmatic_obligations`: it's the obligation for grants and payments, and the subsidy cost for loans. + +### `getAssistanceGrant(key, options?)` (and the other three) + +```ts +const award = await client.getAssistanceGrant("ASST_NON_693JF72440048_069", { + shape: "key,fain,obligated,recipient(uei,legal_business_name),listings(*),obligations_by_fiscal_year(*)", +}); +``` + +Takes the award's `key`. A retired key (one in the award's `former_award_keys`) returns the award that carries it now, with its current `key` in the body. An award lives under one group's endpoint; if its assistance type moves to another group, its old URL returns 404. Defaults to `ShapeConfig.ASSISTANCE_AWARDS_MINIMAL`. + +- `recipient` is a shared recipient record under `Entity`'s field names, and is `null` on aggregate and PII-redacted records (record types `1` and `3`). Its `business_types` are USAspending's recipient-type codes, not SAM.gov's. +- `listings(...)`, `transactions(...)`, `officers(...)`, `recipient_address(...)` and `obligations_by_fiscal_year(...)` are available only when named in `shape`. +- Dates are parsed to `Date` and money fields to decimal strings. The `AssistanceAward` interface in `@makegov/tango-node/models` lists every field. + +### `listAssistanceGrantTransactions(key, options?)` / `listAssistanceGrantListings(key, options?)` (and the other three) + +```ts +const transactions = await client.listAssistanceGrantTransactions("ASST_NON_693JF72440048_069", { limit: 100 }); +const listings = await client.listAssistanceGrantListings("ASST_NON_693JF72440048_069"); +``` + +An award's transactions, latest first, and its money broken out by assistance listing, primary listing first. Both are page-numbered (`page`, `limit`) and unshaped: rows come back as the API sends them, typed `AssistanceTransaction` and `AssistanceAwardListing`. Transactions default to 100 per page and cap at 500; listings default to 25 and cap at 100. + +--- + ## GSA eLibrary Contracts ### `listGsaElibraryContracts(options?)` diff --git a/docs/SHAPES.md b/docs/SHAPES.md index 8847a53..46b5d78 100644 --- a/docs/SHAPES.md +++ b/docs/SHAPES.md @@ -103,6 +103,7 @@ import { TangoClient, ShapeConfig } from "@makegov/tango-node"; | `ShapeConfig.SBIR_SOLICITATIONS_MINIMAL` | `listSbirSolicitations()` | | `ShapeConfig.FEDERAL_REGISTER_MINIMAL` | `listFederalRegisterDocuments()` | | `ShapeConfig.FEDERAL_REGISTER_COMPREHENSIVE` | `getFederalRegisterDocument()` | +| `ShapeConfig.ASSISTANCE_AWARDS_MINIMAL` | `listAssistanceGrants()` / `getAssistanceGrant()` and the loans, direct-payments and other-assistance equivalents | 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 cfcf59e..51566a9 100644 --- a/scripts/check-filter-shape-conformance.ts +++ b/scripts/check-filter-shape-conformance.ts @@ -70,6 +70,10 @@ export const RESOURCE_TO_METHOD: Record = { protests: "listProtests", contract_appeals: "listContractAppeals", federal_register: "listFederalRegisterDocuments", + "assistance/grants": "listAssistanceGrants", + "assistance/loans": "listAssistanceLoans", + "assistance/direct_payments": "listAssistanceDirectPayments", + "assistance/other": "listAssistanceOther", "ebuy/requests": "listEbuyRequests", psc: "listPsc", mas_sins: "listMasSins", @@ -144,6 +148,7 @@ const SHAPE_CONFIG_ENTRIES: ShapeEntry[] = [ { shapeName: "EXCLUSIONS_MINIMAL", modelName: "Exclusion" }, { shapeName: "SBIR_TOPICS_MINIMAL", modelName: "SbirTopic" }, { shapeName: "SBIR_SOLICITATIONS_MINIMAL", modelName: "SbirSolicitation" }, + { shapeName: "ASSISTANCE_AWARDS_MINIMAL", modelName: "AssistanceAward" }, ]; // --------------------------------------------------------------------------- diff --git a/scripts/check-shape-coverage.ts b/scripts/check-shape-coverage.ts index 1af7e1c..2061108 100644 --- a/scripts/check-shape-coverage.ts +++ b/scripts/check-shape-coverage.ts @@ -68,6 +68,10 @@ export const RESOURCE_TO_MODEL: Record = { protests: "Protest", contract_appeals: "ContractAppeal", federal_register: "FederalRegisterDocument", + "assistance/grants": "AssistanceAward", + "assistance/loans": "AssistanceAward", + "assistance/direct_payments": "AssistanceAward", + "assistance/other": "AssistanceAward", "ebuy/requests": "EbuyRequest", offices: "Office", assistance_listings: "AssistanceListing", diff --git a/src/client.ts b/src/client.ts index c7187e2..caebbec 100644 --- a/src/client.ts +++ b/src/client.ts @@ -18,6 +18,7 @@ import { TangoClientOptions, ValidateResult, } from "./types.js"; +import type { AssistanceAwardGroup, AssistanceAwardListing, AssistanceTransaction } from "./models/AssistanceAward.js"; import type { WebhookAlert, WebhookAlertCreateInput, @@ -845,7 +846,11 @@ export type IterableListMethod = | "listSbirTopics" | "listSbirSolicitations" | "listSledOpportunities" - | "listSledForecasts"; + | "listSledForecasts" + | "listAssistanceGrants" + | "listAssistanceLoans" + | "listAssistanceDirectPayments" + | "listAssistanceOther"; // --------------------------------------------------------------------------- // Read-method option interfaces (lookups + awards completeness + other) @@ -1109,6 +1114,98 @@ export interface ListEbuyRequestsOptions extends ListOptionsBase { [key: string]: unknown; } +/** + * Filters for the four financial-assistance award lists: `listAssistanceGrants`, `listAssistanceLoans`, `listAssistanceDirectPayments` and `listAssistanceOther`. + * + * The lists are cursor-paginated (keyset): pass the `cursor` from a previous page's response, or use the matching `iterate*` method. Filters are sent under the API's own param names. An unknown param, or a one-character `search`, `awarding_agency` or `funding_agency`, returns 400. Requires Tango API 5.12.0. + */ +export interface ListAssistanceAwardsOptions { + /** Page size, capped at 100. Defaults to 25. */ + limit?: number; + /** Keyset cursor from a previous page's `next` URL (exposed as `cursor` on the response). */ + cursor?: string | null; + shape?: string | null; + flat?: boolean; + flatLists?: boolean; + /** Separator for flattened keys. Only meaningful alongside `flat`. */ + joiner?: string; + + /** Award key, exact. Multi-value OR via `|`. */ + key?: string; + /** Federal Award Identification Number, exact. Multi-value OR via `|`. */ + fain?: string; + /** Unique Record Identifier, carried by aggregate records, exact. Multi-value OR via `|`. */ + uri?: string; + /** The funding opportunity number the award was made under, as on the Grants.gov opportunity, exact. Multi-value OR via `|`. */ + funding_opportunity_number?: string; + + /** Recipient UEI, exact. Multi-value OR via `|`. */ + uei?: string; + /** Recipient name or UEI. Each term matches the recipients closest to it by name among those with an award in this group; a term under three characters returns 400. Multi-value OR via `|` only (not the word `OR`). To pin one recipient, filter on `uei`. */ + recipient?: string; + + /** Awarding agency by code, name or abbreviation (best-effort matching). Multi-value OR via `|`. */ + awarding_agency?: string; + /** Funding agency or department, matched like `awarding_agency`. Multi-value OR via `|`. */ + funding_agency?: string; + + /** The award's primary assistance listing (CFDA) number, e.g. `93.778`. Multi-value OR via `|`. */ + cfda_number?: string; + /** GSDM assistance type code (`F001`-`F010`), case-insensitive. Multi-value OR via `|`. A code outside the group's types matches nothing. */ + assistance_type?: string; + /** Record type: `1` aggregate, `2` non-aggregate, `3` individual recipient (PII-redacted). Multi-value OR via `|`. */ + record_type?: string; + /** Business funds indicator: `NON` (not Recovery Act) or `REC` (Recovery Act), case-insensitive. */ + business_funds_indicator?: string; + /** Disaster Emergency Fund Code, e.g. `L`, case-insensitive; matches the code as a whole within the award's list. Multi-value OR via `|`. */ + disaster_emergency_fund_code?: string; + + /** Award date, exact (YYYY-MM-DD). */ + award_date?: string; + /** Award date on or after (YYYY-MM-DD). */ + award_date_gte?: string; + /** Award date on or before (YYYY-MM-DD). */ + award_date_lte?: string; + /** Federal fiscal year of the award, exact. */ + fiscal_year?: number | string; + fiscal_year_gte?: number | string; + fiscal_year_lte?: number | string; + /** Period of performance start date on or after (YYYY-MM-DD). */ + pop_start_date_gte?: string; + /** Period of performance start date on or before (YYYY-MM-DD). */ + pop_start_date_lte?: string; + /** Period of performance current end date on or after (YYYY-MM-DD). */ + pop_end_date_gte?: string; + /** Period of performance current end date on or before (YYYY-MM-DD). */ + pop_end_date_lte?: string; + + /** Place of performance state code, e.g. `VA`, case-insensitive. Multi-value OR via `|`. */ + pop_state?: string; + /** Place of performance country code, ISO 3166 alpha-3, e.g. `USA`, case-insensitive. Multi-value OR via `|`. */ + pop_country?: string; + /** Place of performance five-digit ZIP code, exact. Multi-value OR via `|`. */ + pop_zip?: string; + + /** Obligated amount greater than or equal to (USD). */ + obligated_gte?: number | string; + /** Obligated amount less than or equal to (USD). */ + obligated_lte?: number | string; + + /** Full-text search over identifiers, recipient names, assistance listing titles and award descriptions. Web-search syntax: `a b`, `a or b`, `a -b`. */ + search?: string; + /** `award_date`, `obligated` or `pragmatic_obligations`; prefix `-` for descending. Defaults to `-award_date`. Order by `pragmatic_obligations` to compare across groups. */ + ordering?: string; + + [key: string]: unknown; +} + +/** Pagination for an assistance award's `/transactions/` and `/listings/` sub-routes, which are page-numbered. */ +export interface AssistanceAwardSubresourceOptions { + page?: number; + /** Page size. `/transactions/` defaults to 100 and caps at 500; `/listings/` defaults to 25 here and caps at 100. */ + limit?: number; +} + export interface ListItDashboardOptions { page?: number; limit?: number; @@ -2275,6 +2372,22 @@ export class TangoClient { return this.iterate>("listSledForecasts", options); } + iterateAssistanceGrants(options: ListAssistanceAwardsOptions = {}): AsyncIterableIterator> { + return this.iterate>("listAssistanceGrants", options); + } + + iterateAssistanceLoans(options: ListAssistanceAwardsOptions = {}): AsyncIterableIterator> { + return this.iterate>("listAssistanceLoans", options); + } + + iterateAssistanceDirectPayments(options: ListAssistanceAwardsOptions = {}): AsyncIterableIterator> { + return this.iterate>("listAssistanceDirectPayments", options); + } + + iterateAssistanceOther(options: ListAssistanceAwardsOptions = {}): AsyncIterableIterator> { + return this.iterate>("listAssistanceOther", options); + } + // --------------------------------------------------------------------------- // Lookups // --------------------------------------------------------------------------- @@ -2865,6 +2978,200 @@ export class TangoClient { ); } + // --------------------------------------------------------------------------- + // Financial-assistance awards (`/api/assistance/{group}/`) + // + // Four groups by GSDM assistance type, one shape, one filter set. The lists + // are cursor-paginated; the `/transactions/` and `/listings/` sub-routes are + // page-numbered. + // --------------------------------------------------------------------------- + + /** + * List grant and cooperative-agreement awards (`/api/assistance/grants/`; assistance types `F001`, `F002`). Requires Tango API 5.12.0. + * + * These are awards an agency has already obligated, from USAspending. Grant opportunities you can apply for are `listGrants()`; an award's `funding_opportunity_number` names the opportunity it was made under. + */ + async listAssistanceGrants(options: ListAssistanceAwardsOptions = {}): Promise>> { + return this._assistanceAwardList("grants", options); + } + + /** Get a grant or cooperative-agreement award by `key` (`/api/assistance/grants/{key}/`). A retired key resolves to the award that carries it now. */ + async getAssistanceGrant( + key: string, + options: { shape?: string | null; flat?: boolean; flatLists?: boolean; joiner?: string } = {}, + ): Promise> { + return this._assistanceAwardGet("grants", key, options); + } + + /** List a grant award's transactions, latest first (`/api/assistance/grants/{key}/transactions/`). */ + async listAssistanceGrantTransactions( + key: string, + options: AssistanceAwardSubresourceOptions = {}, + ): Promise> { + return this._assistanceAwardTransactions("grants", key, options); + } + + /** List a grant award's money by assistance listing, primary listing first (`/api/assistance/grants/{key}/listings/`). */ + async listAssistanceGrantListings( + key: string, + options: AssistanceAwardSubresourceOptions = {}, + ): Promise> { + return this._assistanceAwardListings("grants", key, options); + } + + /** + * List loan awards (`/api/assistance/loans/`; direct loans and loan guarantees, `F003`, `F004`). Requires Tango API 5.12.0. + * + * On a loan, `pragmatic_obligations` is the subsidy cost, the figure comparable with the other groups; `face_value_of_loan` is the loan's face value. + */ + async listAssistanceLoans(options: ListAssistanceAwardsOptions = {}): Promise>> { + return this._assistanceAwardList("loans", options); + } + + /** Get a loan award by `key` (`/api/assistance/loans/{key}/`). A retired key resolves to the award that carries it now. */ + async getAssistanceLoan( + key: string, + options: { shape?: string | null; flat?: boolean; flatLists?: boolean; joiner?: string } = {}, + ): Promise> { + return this._assistanceAwardGet("loans", key, options); + } + + /** List a loan award's transactions, latest first (`/api/assistance/loans/{key}/transactions/`). */ + async listAssistanceLoanTransactions( + key: string, + options: AssistanceAwardSubresourceOptions = {}, + ): Promise> { + return this._assistanceAwardTransactions("loans", key, options); + } + + /** List a loan award's money by assistance listing, primary listing first (`/api/assistance/loans/{key}/listings/`). */ + async listAssistanceLoanListings(key: string, options: AssistanceAwardSubresourceOptions = {}): Promise> { + return this._assistanceAwardListings("loans", key, options); + } + + /** List direct-payment awards (`/api/assistance/direct_payments/`; direct payments for specified or unrestricted use, `F006`, `F007`). Requires Tango API 5.12.0. */ + async listAssistanceDirectPayments(options: ListAssistanceAwardsOptions = {}): Promise>> { + return this._assistanceAwardList("direct_payments", options); + } + + /** Get a direct-payment award by `key` (`/api/assistance/direct_payments/{key}/`). A retired key resolves to the award that carries it now. */ + async getAssistanceDirectPayment( + key: string, + options: { shape?: string | null; flat?: boolean; flatLists?: boolean; joiner?: string } = {}, + ): Promise> { + return this._assistanceAwardGet("direct_payments", key, options); + } + + /** List a direct-payment award's transactions, latest first (`/api/assistance/direct_payments/{key}/transactions/`). */ + async listAssistanceDirectPaymentTransactions( + key: string, + options: AssistanceAwardSubresourceOptions = {}, + ): Promise> { + return this._assistanceAwardTransactions("direct_payments", key, options); + } + + /** List a direct-payment award's money by assistance listing, primary listing first (`/api/assistance/direct_payments/{key}/listings/`). */ + async listAssistanceDirectPaymentListings( + key: string, + options: AssistanceAwardSubresourceOptions = {}, + ): Promise> { + return this._assistanceAwardListings("direct_payments", key, options); + } + + /** List other assistance awards (`/api/assistance/other/`; insurance, asset forfeiture, property sales and other financial assistance, `F005`, `F008`-`F010`). Requires Tango API 5.12.0. */ + async listAssistanceOther(options: ListAssistanceAwardsOptions = {}): Promise>> { + return this._assistanceAwardList("other", options); + } + + /** Get an other-assistance award by `key` (`/api/assistance/other/{key}/`). A retired key resolves to the award that carries it now. */ + async getAssistanceOther( + key: string, + options: { shape?: string | null; flat?: boolean; flatLists?: boolean; joiner?: string } = {}, + ): Promise> { + return this._assistanceAwardGet("other", key, options); + } + + /** List an other-assistance award's transactions, latest first (`/api/assistance/other/{key}/transactions/`). */ + async listAssistanceOtherTransactions( + key: string, + options: AssistanceAwardSubresourceOptions = {}, + ): Promise> { + return this._assistanceAwardTransactions("other", key, options); + } + + /** List an other-assistance award's money by assistance listing, primary listing first (`/api/assistance/other/{key}/listings/`). */ + async listAssistanceOtherListings( + key: string, + options: AssistanceAwardSubresourceOptions = {}, + ): Promise> { + return this._assistanceAwardListings("other", key, options); + } + + /** Cursor-paginated shaped list shared by the four groups. `page` is dropped: the lists are keyset-only. */ + private async _assistanceAwardList( + group: AssistanceAwardGroup, + options: ListAssistanceAwardsOptions, + ): Promise>> { + const { page: _page, cursor, limit = 25, shape, flat = false, flatLists = false, joiner = ".", ...filters } = options; + void _page; + + const params: AnyRecord = { limit: Math.min(limit, 100) }; + if (cursor) params.cursor = cursor; + + const shapeToUse = shape ?? ShapeConfig.ASSISTANCE_AWARDS_MINIMAL; + const shapeSpec = this.parseShape(shapeToUse, flat, flatLists); + if (shapeToUse) { + params.shape = shapeToUse; + if (flat) { + params.flat = "true"; + if (joiner) params.joiner = joiner; + } + if (flatLists) params.flat_lists = "true"; + } + + for (const [k, v] of Object.entries(filters)) { + if (v !== undefined && v !== null) params[k] = v; + } + + const data = await this.http.get(`/api/assistance/${group}/`, params); + const rawResults = Array.isArray(data?.results) ? (data.results as AnyRecord[]) : []; + const results = this.materializeList("AssistanceAward", shapeSpec, rawResults, flat, joiner); + return buildPaginatedResponse({ ...data, results }); + } + + private async _assistanceAwardGet( + group: AssistanceAwardGroup, + key: string, + options: { shape?: string | null; flat?: boolean; flatLists?: boolean; joiner?: string }, + ): Promise> { + if (!key) throw new TangoValidationError("Assistance award key is required"); + return this._shapedGet(`/api/assistance/${group}/${encodeURIComponent(key)}/`, "AssistanceAward", ShapeConfig.ASSISTANCE_AWARDS_MINIMAL, options); + } + + private async _assistanceAwardTransactions( + group: AssistanceAwardGroup, + key: string, + options: AssistanceAwardSubresourceOptions, + ): Promise> { + if (!key) throw new TangoValidationError("Assistance award key is required"); + const { page = 1, limit = 100 } = options; + const params: AnyRecord = { page, limit: Math.min(limit, 500) }; + const data = await this.http.get(`/api/assistance/${group}/${encodeURIComponent(key)}/transactions/`, params); + return buildPaginatedResponse(data); + } + + private async _assistanceAwardListings( + group: AssistanceAwardGroup, + key: string, + options: AssistanceAwardSubresourceOptions, + ): Promise> { + if (!key) throw new TangoValidationError("Assistance award key is required"); + const { page = 1, limit = 25 } = options; + const params: AnyRecord = { page, limit: Math.min(limit, 100) }; + const data = await this.http.get(`/api/assistance/${group}/${encodeURIComponent(key)}/listings/`, params); + return buildPaginatedResponse(data); + } + /** * List GSA eBuy requests (`/api/ebuy/requests/`). Requires the Pro tier or above. * diff --git a/src/config.ts b/src/config.ts index 028160d..4063dea 100644 --- a/src/config.ts +++ b/src/config.ts @@ -43,6 +43,14 @@ export const ShapeConfig = { "executive_order_number,presidential_document_number,proclamation_number," + "comment_url,regulations_dot_gov_url,raw_text_url,body_html_url", + // Default for the list and get methods of all four assistance award groups (listAssistanceGrants(), getAssistanceGrant(), ...). Mirrors the API's own default. + ASSISTANCE_AWARDS_MINIMAL: + "key,fain,uri,award_date,fiscal_year,assistance_type(code,description)," + + "record_type,cfda_number,assistance_listing(number,title),title," + + "obligated,pragmatic_obligations,face_value_of_loan,loan_subsidy_cost," + + "awarding_office(*),funding_office(*),recipient(*),place_of_performance(*)," + + "period_of_performance(start_date,current_end_date)", + // Default for listGrants() GRANTS_MINIMAL: "grant_id,opportunity_number,title,status(*),agency_code", diff --git a/src/index.ts b/src/index.ts index 2254635..975a630 100644 --- a/src/index.ts +++ b/src/index.ts @@ -31,6 +31,8 @@ export type { ListProtestsOptions, ListContractAppealsOptions, ListFederalRegisterDocumentsOptions, + ListAssistanceAwardsOptions, + AssistanceAwardSubresourceOptions, ListEbuyRequestsOptions, ListItDashboardOptions, ListMetricsOptions, diff --git a/src/models/AssistanceAward.ts b/src/models/AssistanceAward.ts new file mode 100644 index 0000000..fb372cc --- /dev/null +++ b/src/models/AssistanceAward.ts @@ -0,0 +1,211 @@ +import type { FiscalYearObligation, OrganizationOfficePayload } from "./Contract.js"; + +/** + * The four financial-assistance award groups, one endpoint each under `/api/assistance/`. + * + * An award lives under the group of its latest action's assistance type; if that type moves to another group, the award moves endpoints with the same `key`. + */ +export type AssistanceAwardGroup = "grants" | "loans" | "direct_payments" | "other"; + +/** A `{code, description}` pair: the award's GSDM assistance type, or one of the recipient's USAspending business types. */ +export interface AssistanceCodeDescription { + code?: string | null; + description?: string | null; +} + +/** The catalog entry for the award's primary assistance listing. Null when the catalog doesn't list the award's `cfda_number`. */ +export interface AssistanceListingRef { + number?: string | null; + title?: string | null; +} + +/** + * An award's recipient: a shared recipient record under `Entity`'s field names. Every award to the recipient points at the same record, including recipients with no UEI. + * + * Null on aggregate and PII-redacted records (record types `1` and `3`). + */ +export interface AssistanceRecipient { + /** Tango's recipient identifier; for a recipient with a UEI it is the entity's uuid. */ + uuid?: string | null; + uei?: string | null; + duns?: string | null; + legal_business_name?: string | null; + legal_business_name_raw?: string | null; + county?: string | null; + congressional_district?: string | null; + congressional_district_original?: string | null; + /** The recipient's highest owner as filed (`uei`, `duns`, `legal_business_name`, `legal_business_name_raw`). */ + highest_owner?: Record | null; + /** USAspending recipient-type codes, not SAM.gov's. */ + business_types?: AssistanceCodeDescription[] | null; +} + +/** The recipient record's address, plus the two congressional districts filed on this award. Null when every key is empty. */ +export interface AssistanceRecipientAddress { + address_line1?: string | null; + address_line2?: string | null; + city?: string | null; + state_or_province_code?: string | null; + zip_code?: string | null; + zip_code_plus4?: string | null; + country_code?: string | null; + county_fips?: string | null; + state_fips?: string | null; + foreign_city_name?: string | null; + foreign_province_name?: string | null; + foreign_postal_code?: string | null; + congressional_district_original?: string | null; + congressional_district_current?: string | null; +} + +/** Where an assistance award is performed. `country` is ISO 3166 alpha-3. */ +export interface AssistancePlaceOfPerformance { + country?: string | null; + state_code?: string | null; + state_name?: string | null; + state_fips?: string | null; + county_fips?: string | null; + county_name?: string | null; + city_name?: string | null; + zip_code?: string | null; + zip_plus_four?: string | null; + code?: string | null; + scope?: string | null; + foreign_location?: string | null; +} + +export interface AssistancePeriodOfPerformance { + start_date?: string | null; + current_end_date?: string | null; +} + +/** The award's five highest-compensated officers, as filed. Null when none were. */ +export interface AssistanceOfficers { + highly_compensated_officer_1_name?: string | null; + highly_compensated_officer_1_amount?: string | number | null; + highly_compensated_officer_2_name?: string | null; + highly_compensated_officer_2_amount?: string | number | null; + highly_compensated_officer_3_name?: string | null; + highly_compensated_officer_3_amount?: string | number | null; + highly_compensated_officer_4_name?: string | null; + highly_compensated_officer_4_amount?: string | number | null; + highly_compensated_officer_5_name?: string | null; + highly_compensated_officer_5_amount?: string | number | null; +} + +/** + * One action on an assistance award: an element of the `transactions(...)` expand, or a row of the `/transactions/` sub-route. + * + * Money fields arrive as JSON numbers; the client's shaped response parses them to decimal strings. The sub-route returns rows unshaped, as the API sends them. + */ +export interface AssistanceTransaction { + key?: string; + modification_number?: string | null; + transaction_date?: string | null; + action_type?: string | null; + assistance_type_code?: string | null; + correction_delete_indicator?: string | null; + cfda_number?: string | null; + obligated?: string | number | null; + pragmatic_obligations?: string | number | null; + non_federal_funding_amount?: string | number | null; + indirect_cost_federal_share_amount?: string | number | null; + face_value_of_loan?: string | number | null; + loan_subsidy_cost?: string | number | null; + award_amount_basis?: string | null; + award_recipient_basis?: string | null; + /** A number on the `/transactions/` sub-route, a string such as `"2027"` in the `transactions(...)` expand. */ + action_date_fiscal_year?: number | string | null; + last_modified_date?: string | null; + initial_report_date?: string | null; +} + +/** + * An award's money under one assistance listing: an element of the `listings(...)` expand, or a row of the `/listings/` sub-route. The primary listing comes first. + */ +export interface AssistanceAwardListing { + cfda_number: string | null; + is_primary?: boolean; + obligated?: string | number | null; + pragmatic_obligations?: string | number | null; + non_federal_funding_amount?: string | number | null; + indirect_cost_federal_share_amount?: string | number | null; + face_value_of_loan?: string | number | null; + loan_subsidy_cost?: string | number | null; + transactions_count?: number | null; + first_action_date?: string | null; + last_action_date?: string | null; +} + +/** + * A financial-assistance award (`/api/assistance/{grants,loans,direct_payments,other}/`), from USAspending. All four groups share this shape. + * + * The endpoints use shape-on-demand: which fields appear depends on the `?shape=` query param, so EVERY field is optional. + * + * `key` is the award identifier every detail and sub-route URL takes; a retired key in `former_award_keys` still resolves to the award that carries it now. + * + * Date fields are typed as the API's ISO strings and money fields as the JSON numbers the API sends; a shaped response from the client parses dates to `Date` and money to decimal strings. To compare dollar amounts across the four groups, read `pragmatic_obligations`: the obligation for grants and payments, the subsidy cost for loans. + */ +export interface AssistanceAward { + key?: string; + fain?: string | null; + uri?: string | null; + award_date?: string | null; + last_action_date?: string | null; + last_modified_date?: string | null; + fiscal_year?: number | null; + /** The GSDM assistance type (`F001`-`F010`) with its description. */ + assistance_type?: AssistanceCodeDescription | null; + /** `1` aggregate, `2` non-aggregate, `3` individual recipient (PII-redacted). */ + record_type?: string | null; + /** The primary assistance listing (CFDA) number. */ + cfda_number?: string | null; + assistance_listing?: AssistanceListingRef | null; + title?: string | null; + summary?: string | null; + purposes_differ?: boolean | null; + obligated?: string | number | null; + pragmatic_obligations?: string | number | null; + non_federal_funding_amount?: string | number | null; + indirect_cost_federal_share_amount?: string | number | null; + face_value_of_loan?: string | number | null; + loan_subsidy_cost?: string | number | null; + insurance_coverage_amount?: string | number | null; + award_total_obligated?: string | number | null; + award_total_outlayed?: string | number | null; + award_total_non_federal_funding?: string | number | null; + award_total_face_value_of_loan?: string | number | null; + award_total_loan_subsidy_cost?: string | number | null; + covid19_obligated?: string | number | null; + covid19_outlayed?: string | number | null; + iija_obligated?: string | number | null; + iija_outlayed?: string | number | null; + transactions_count?: number | null; + listings_count?: number | null; + assistance_types_count?: number | null; + award_amount_basis?: string | null; + award_recipient_basis?: string | null; + /** `NON` (not Recovery Act) or `REC` (Recovery Act). */ + business_funds_indicator?: string | null; + /** The Disaster Emergency Fund Codes as USAspending publishes them: free text such as `"AAK: Emergency P.L. 118-158"`, not a list. */ + disaster_emergency_fund_codes?: string | null; + funding_opportunity_number?: string | null; + funding_opportunity_goals_text?: string | null; + sai_number?: string | null; + /** Award IDs USAspending retired in favor of this award's `key`. */ + former_award_keys?: string[]; + treasury_accounts_funding_this_award?: string | null; + federal_accounts_funding_this_award?: string | null; + object_classes_funding_this_award?: string | null; + program_activities_funding_this_award?: string | null; + awarding_office?: OrganizationOfficePayload | null; + funding_office?: OrganizationOfficePayload | null; + recipient?: AssistanceRecipient | null; + recipient_address?: AssistanceRecipientAddress | null; + place_of_performance?: AssistancePlaceOfPerformance | null; + period_of_performance?: AssistancePeriodOfPerformance | null; + officers?: AssistanceOfficers | null; + obligations_by_fiscal_year?: FiscalYearObligation[] | null; + listings?: AssistanceAwardListing[]; + transactions?: AssistanceTransaction[]; +} diff --git a/src/models/index.ts b/src/models/index.ts index affb17b..0504378 100644 --- a/src/models/index.ts +++ b/src/models/index.ts @@ -1,4 +1,17 @@ export type { Agency } from "./Agency.js"; +export type { + AssistanceAward, + AssistanceAwardGroup, + AssistanceAwardListing, + AssistanceCodeDescription, + AssistanceListingRef, + AssistanceOfficers, + AssistancePeriodOfPerformance, + AssistancePlaceOfPerformance, + AssistanceRecipient, + AssistanceRecipientAddress, + AssistanceTransaction, +} from "./AssistanceAward.js"; export type { BudgetAccount, BudgetSourceAnomaly, BudgetSourceAnomalyRow, BudgetSourceAnomalySource } from "./BudgetAccount.js"; export type { Contract, diff --git a/src/shapes/explicitSchemas.ts b/src/shapes/explicitSchemas.ts index bb8d22c..66a535e 100644 --- a/src/shapes/explicitSchemas.ts +++ b/src/shapes/explicitSchemas.ts @@ -6501,6 +6501,884 @@ export const FEDERAL_REGISTER_DOCUMENT_SCHEMA: FieldSchemaMap = { }, }; +// Financial-assistance awards (`/api/assistance/{grants,loans,direct_payments,other}/`). The four groups share one shape. +// The award's recipient: a shared recipient record under `Entity`'s field names. `business_types` is a list of `{code, description}`. +export const ASSISTANCE_RECIPIENT_SCHEMA: FieldSchemaMap = { + uuid: { + name: "uuid", + type: "str", + isOptional: true, + isList: false, + nestedModel: null, + }, + uei: { + name: "uei", + type: "str", + isOptional: true, + isList: false, + nestedModel: null, + }, + duns: { + name: "duns", + type: "str", + isOptional: true, + isList: false, + nestedModel: null, + }, + legal_business_name: { + name: "legal_business_name", + type: "str", + isOptional: true, + isList: false, + nestedModel: null, + }, + legal_business_name_raw: { + name: "legal_business_name_raw", + type: "str", + isOptional: true, + isList: false, + nestedModel: null, + }, + county: { + name: "county", + type: "str", + isOptional: true, + isList: false, + nestedModel: null, + }, + congressional_district: { + name: "congressional_district", + type: "str", + isOptional: true, + isList: false, + nestedModel: null, + }, + congressional_district_original: { + name: "congressional_district_original", + type: "str", + isOptional: true, + isList: false, + nestedModel: null, + }, + highest_owner: { + name: "highest_owner", + type: "dict", + isOptional: true, + isList: false, + nestedModel: null, + }, + business_types: { + name: "business_types", + type: "dict", + isOptional: true, + isList: true, + nestedModel: null, + }, +}; + +export const ASSISTANCE_RECIPIENT_ADDRESS_SCHEMA: FieldSchemaMap = { + address_line1: { + name: "address_line1", + type: "str", + isOptional: true, + isList: false, + nestedModel: null, + }, + address_line2: { + name: "address_line2", + type: "str", + isOptional: true, + isList: false, + nestedModel: null, + }, + city: { + name: "city", + type: "str", + isOptional: true, + isList: false, + nestedModel: null, + }, + state_or_province_code: { + name: "state_or_province_code", + type: "str", + isOptional: true, + isList: false, + nestedModel: null, + }, + zip_code: { + name: "zip_code", + type: "str", + isOptional: true, + isList: false, + nestedModel: null, + }, + zip_code_plus4: { + name: "zip_code_plus4", + type: "str", + isOptional: true, + isList: false, + nestedModel: null, + }, + country_code: { + name: "country_code", + type: "str", + isOptional: true, + isList: false, + nestedModel: null, + }, + county_fips: { + name: "county_fips", + type: "str", + isOptional: true, + isList: false, + nestedModel: null, + }, + state_fips: { + name: "state_fips", + type: "str", + isOptional: true, + isList: false, + nestedModel: null, + }, + foreign_city_name: { + name: "foreign_city_name", + type: "str", + isOptional: true, + isList: false, + nestedModel: null, + }, + foreign_province_name: { + name: "foreign_province_name", + type: "str", + isOptional: true, + isList: false, + nestedModel: null, + }, + foreign_postal_code: { + name: "foreign_postal_code", + type: "str", + isOptional: true, + isList: false, + nestedModel: null, + }, + congressional_district_original: { + name: "congressional_district_original", + type: "str", + isOptional: true, + isList: false, + nestedModel: null, + }, + congressional_district_current: { + name: "congressional_district_current", + type: "str", + isOptional: true, + isList: false, + nestedModel: null, + }, +}; + +export const ASSISTANCE_PLACE_OF_PERFORMANCE_SCHEMA: FieldSchemaMap = { + country: { + name: "country", + type: "str", + isOptional: true, + isList: false, + nestedModel: null, + }, + state_code: { + name: "state_code", + type: "str", + isOptional: true, + isList: false, + nestedModel: null, + }, + state_name: { + name: "state_name", + type: "str", + isOptional: true, + isList: false, + nestedModel: null, + }, + state_fips: { + name: "state_fips", + type: "str", + isOptional: true, + isList: false, + nestedModel: null, + }, + county_fips: { + name: "county_fips", + type: "str", + isOptional: true, + isList: false, + nestedModel: null, + }, + county_name: { + name: "county_name", + type: "str", + isOptional: true, + isList: false, + nestedModel: null, + }, + city_name: { + name: "city_name", + type: "str", + isOptional: true, + isList: false, + nestedModel: null, + }, + zip_code: { + name: "zip_code", + type: "str", + isOptional: true, + isList: false, + nestedModel: null, + }, + zip_plus_four: { + name: "zip_plus_four", + type: "str", + isOptional: true, + isList: false, + nestedModel: null, + }, + code: { + name: "code", + type: "str", + isOptional: true, + isList: false, + nestedModel: null, + }, + scope: { + name: "scope", + type: "str", + isOptional: true, + isList: false, + nestedModel: null, + }, + foreign_location: { + name: "foreign_location", + type: "str", + isOptional: true, + isList: false, + nestedModel: null, + }, +}; + +export const ASSISTANCE_PERIOD_OF_PERFORMANCE_SCHEMA: FieldSchemaMap = { + start_date: { + name: "start_date", + type: "date", + isOptional: true, + isList: false, + nestedModel: null, + }, + current_end_date: { + name: "current_end_date", + type: "date", + isOptional: true, + isList: false, + nestedModel: null, + }, +}; + +export const ASSISTANCE_AWARD_LISTING_SCHEMA: FieldSchemaMap = { + cfda_number: { + name: "cfda_number", + type: "str", + isOptional: true, + isList: false, + nestedModel: null, + }, + is_primary: { + name: "is_primary", + type: "bool", + isOptional: true, + isList: false, + nestedModel: null, + }, + obligated: { + name: "obligated", + type: "Decimal", + isOptional: true, + isList: false, + nestedModel: null, + }, + pragmatic_obligations: { + name: "pragmatic_obligations", + type: "Decimal", + isOptional: true, + isList: false, + nestedModel: null, + }, + non_federal_funding_amount: { + name: "non_federal_funding_amount", + type: "Decimal", + isOptional: true, + isList: false, + nestedModel: null, + }, + indirect_cost_federal_share_amount: { + name: "indirect_cost_federal_share_amount", + type: "Decimal", + isOptional: true, + isList: false, + nestedModel: null, + }, + face_value_of_loan: { + name: "face_value_of_loan", + type: "Decimal", + isOptional: true, + isList: false, + nestedModel: null, + }, + loan_subsidy_cost: { + name: "loan_subsidy_cost", + type: "Decimal", + isOptional: true, + isList: false, + nestedModel: null, + }, + transactions_count: { + name: "transactions_count", + type: "int", + isOptional: true, + isList: false, + nestedModel: null, + }, + first_action_date: { + name: "first_action_date", + type: "date", + isOptional: true, + isList: false, + nestedModel: null, + }, + last_action_date: { + name: "last_action_date", + type: "date", + isOptional: true, + isList: false, + nestedModel: null, + }, +}; + +// One element of an assistance award's transactions payload, as tango-etl writes it. `action_date_fiscal_year` arrives as a string here (`"2027"`) and as a number on the `/transactions/` sub-route. +export const ASSISTANCE_TRANSACTION_SCHEMA: FieldSchemaMap = { + key: { + name: "key", + type: "str", + isOptional: true, + isList: false, + nestedModel: null, + }, + modification_number: { + name: "modification_number", + type: "str", + isOptional: true, + isList: false, + nestedModel: null, + }, + transaction_date: { + name: "transaction_date", + type: "date", + isOptional: true, + isList: false, + nestedModel: null, + }, + action_type: { + name: "action_type", + type: "str", + isOptional: true, + isList: false, + nestedModel: null, + }, + assistance_type_code: { + name: "assistance_type_code", + type: "str", + isOptional: true, + isList: false, + nestedModel: null, + }, + correction_delete_indicator: { + name: "correction_delete_indicator", + type: "str", + isOptional: true, + isList: false, + nestedModel: null, + }, + cfda_number: { + name: "cfda_number", + type: "str", + isOptional: true, + isList: false, + nestedModel: null, + }, + obligated: { + name: "obligated", + type: "Decimal", + isOptional: true, + isList: false, + nestedModel: null, + }, + pragmatic_obligations: { + name: "pragmatic_obligations", + type: "Decimal", + isOptional: true, + isList: false, + nestedModel: null, + }, + non_federal_funding_amount: { + name: "non_federal_funding_amount", + type: "Decimal", + isOptional: true, + isList: false, + nestedModel: null, + }, + indirect_cost_federal_share_amount: { + name: "indirect_cost_federal_share_amount", + type: "Decimal", + isOptional: true, + isList: false, + nestedModel: null, + }, + face_value_of_loan: { + name: "face_value_of_loan", + type: "Decimal", + isOptional: true, + isList: false, + nestedModel: null, + }, + loan_subsidy_cost: { + name: "loan_subsidy_cost", + type: "Decimal", + isOptional: true, + isList: false, + nestedModel: null, + }, + award_amount_basis: { + name: "award_amount_basis", + type: "str", + isOptional: true, + isList: false, + nestedModel: null, + }, + award_recipient_basis: { + name: "award_recipient_basis", + type: "str", + isOptional: true, + isList: false, + nestedModel: null, + }, + action_date_fiscal_year: { + name: "action_date_fiscal_year", + type: "int", + isOptional: true, + isList: false, + nestedModel: null, + }, + last_modified_date: { + name: "last_modified_date", + type: "datetime", + isOptional: true, + isList: false, + nestedModel: null, + }, + initial_report_date: { + name: "initial_report_date", + type: "datetime", + isOptional: true, + isList: false, + nestedModel: null, + }, +}; + +export const ASSISTANCE_AWARD_SCHEMA: FieldSchemaMap = { + key: { + name: "key", + type: "str", + isOptional: true, + isList: false, + nestedModel: null, + }, + fain: { + name: "fain", + type: "str", + isOptional: true, + isList: false, + nestedModel: null, + }, + uri: { + name: "uri", + type: "str", + isOptional: true, + isList: false, + nestedModel: null, + }, + award_date: { + name: "award_date", + type: "date", + isOptional: true, + isList: false, + nestedModel: null, + }, + last_action_date: { + name: "last_action_date", + type: "date", + isOptional: true, + isList: false, + nestedModel: null, + }, + last_modified_date: { + name: "last_modified_date", + type: "datetime", + isOptional: true, + isList: false, + nestedModel: null, + }, + fiscal_year: { + name: "fiscal_year", + type: "int", + isOptional: true, + isList: false, + nestedModel: null, + }, + assistance_type: { + name: "assistance_type", + type: "dict", + isOptional: true, + isList: false, + nestedModel: "CodeDescription", + }, + record_type: { + name: "record_type", + type: "str", + isOptional: true, + isList: false, + nestedModel: null, + }, + cfda_number: { + name: "cfda_number", + type: "str", + isOptional: true, + isList: false, + nestedModel: null, + }, + title: { + name: "title", + type: "str", + isOptional: true, + isList: false, + nestedModel: null, + }, + summary: { + name: "summary", + type: "str", + isOptional: true, + isList: false, + nestedModel: null, + }, + purposes_differ: { + name: "purposes_differ", + type: "bool", + isOptional: true, + isList: false, + nestedModel: null, + }, + obligated: { + name: "obligated", + type: "Decimal", + isOptional: true, + isList: false, + nestedModel: null, + }, + pragmatic_obligations: { + name: "pragmatic_obligations", + type: "Decimal", + isOptional: true, + isList: false, + nestedModel: null, + }, + non_federal_funding_amount: { + name: "non_federal_funding_amount", + type: "Decimal", + isOptional: true, + isList: false, + nestedModel: null, + }, + indirect_cost_federal_share_amount: { + name: "indirect_cost_federal_share_amount", + type: "Decimal", + isOptional: true, + isList: false, + nestedModel: null, + }, + face_value_of_loan: { + name: "face_value_of_loan", + type: "Decimal", + isOptional: true, + isList: false, + nestedModel: null, + }, + loan_subsidy_cost: { + name: "loan_subsidy_cost", + type: "Decimal", + isOptional: true, + isList: false, + nestedModel: null, + }, + insurance_coverage_amount: { + name: "insurance_coverage_amount", + type: "Decimal", + isOptional: true, + isList: false, + nestedModel: null, + }, + award_total_obligated: { + name: "award_total_obligated", + type: "Decimal", + isOptional: true, + isList: false, + nestedModel: null, + }, + award_total_outlayed: { + name: "award_total_outlayed", + type: "Decimal", + isOptional: true, + isList: false, + nestedModel: null, + }, + award_total_non_federal_funding: { + name: "award_total_non_federal_funding", + type: "Decimal", + isOptional: true, + isList: false, + nestedModel: null, + }, + award_total_face_value_of_loan: { + name: "award_total_face_value_of_loan", + type: "Decimal", + isOptional: true, + isList: false, + nestedModel: null, + }, + award_total_loan_subsidy_cost: { + name: "award_total_loan_subsidy_cost", + type: "Decimal", + isOptional: true, + isList: false, + nestedModel: null, + }, + covid19_obligated: { + name: "covid19_obligated", + type: "Decimal", + isOptional: true, + isList: false, + nestedModel: null, + }, + covid19_outlayed: { + name: "covid19_outlayed", + type: "Decimal", + isOptional: true, + isList: false, + nestedModel: null, + }, + iija_obligated: { + name: "iija_obligated", + type: "Decimal", + isOptional: true, + isList: false, + nestedModel: null, + }, + iija_outlayed: { + name: "iija_outlayed", + type: "Decimal", + isOptional: true, + isList: false, + nestedModel: null, + }, + transactions_count: { + name: "transactions_count", + type: "int", + isOptional: true, + isList: false, + nestedModel: null, + }, + listings_count: { + name: "listings_count", + type: "int", + isOptional: true, + isList: false, + nestedModel: null, + }, + assistance_types_count: { + name: "assistance_types_count", + type: "int", + isOptional: true, + isList: false, + nestedModel: null, + }, + award_amount_basis: { + name: "award_amount_basis", + type: "str", + isOptional: true, + isList: false, + nestedModel: null, + }, + award_recipient_basis: { + name: "award_recipient_basis", + type: "str", + isOptional: true, + isList: false, + nestedModel: null, + }, + business_funds_indicator: { + name: "business_funds_indicator", + type: "str", + isOptional: true, + isList: false, + nestedModel: null, + }, + disaster_emergency_fund_codes: { + name: "disaster_emergency_fund_codes", + type: "str", + isOptional: true, + isList: false, + nestedModel: null, + }, + funding_opportunity_number: { + name: "funding_opportunity_number", + type: "str", + isOptional: true, + isList: false, + nestedModel: null, + }, + funding_opportunity_goals_text: { + name: "funding_opportunity_goals_text", + type: "str", + isOptional: true, + isList: false, + nestedModel: null, + }, + sai_number: { + name: "sai_number", + type: "str", + isOptional: true, + isList: false, + nestedModel: null, + }, + former_award_keys: { + name: "former_award_keys", + type: "str", + isOptional: true, + isList: true, + nestedModel: null, + }, + treasury_accounts_funding_this_award: { + name: "treasury_accounts_funding_this_award", + type: "str", + isOptional: true, + isList: false, + nestedModel: null, + }, + federal_accounts_funding_this_award: { + name: "federal_accounts_funding_this_award", + type: "str", + isOptional: true, + isList: false, + nestedModel: null, + }, + object_classes_funding_this_award: { + name: "object_classes_funding_this_award", + type: "str", + isOptional: true, + isList: false, + nestedModel: null, + }, + program_activities_funding_this_award: { + name: "program_activities_funding_this_award", + type: "str", + isOptional: true, + isList: false, + nestedModel: null, + }, + recipient: { + name: "recipient", + type: "dict", + isOptional: true, + isList: false, + nestedModel: "AssistanceRecipient", + }, + recipient_address: { + name: "recipient_address", + type: "dict", + isOptional: true, + isList: false, + nestedModel: "AssistanceRecipientAddress", + }, + place_of_performance: { + name: "place_of_performance", + type: "dict", + isOptional: true, + isList: false, + nestedModel: "AssistancePlaceOfPerformance", + }, + awarding_office: { + name: "awarding_office", + type: "dict", + isOptional: true, + isList: false, + nestedModel: "OrganizationOffice", + }, + funding_office: { + name: "funding_office", + type: "dict", + isOptional: true, + isList: false, + nestedModel: "OrganizationOffice", + }, + assistance_listing: { + name: "assistance_listing", + type: "dict", + isOptional: true, + isList: false, + nestedModel: "CFDANumber", + }, + period_of_performance: { + name: "period_of_performance", + type: "dict", + isOptional: true, + isList: false, + nestedModel: "AssistancePeriodOfPerformance", + }, + officers: { + name: "officers", + type: "dict", + isOptional: true, + isList: false, + nestedModel: "Officers", + }, + obligations_by_fiscal_year: { + name: "obligations_by_fiscal_year", + type: "dict", + isOptional: true, + isList: true, + nestedModel: "FiscalYearObligation", + }, + listings: { + name: "listings", + type: "dict", + isOptional: true, + isList: true, + nestedModel: "AssistanceAwardListing", + }, + transactions: { + name: "transactions", + type: "dict", + isOptional: true, + isList: true, + nestedModel: "AssistanceTransaction", + }, +}; + export const EXPLICIT_SCHEMAS: ExplicitSchemas = { Office: OFFICE_SCHEMA, Location: LOCATION_SCHEMA, @@ -6531,6 +7409,13 @@ export const EXPLICIT_SCHEMAS: ExplicitSchemas = { ProtestDocket: PROTEST_DOCKET_SCHEMA, ContractAppeal: CONTRACT_APPEAL_SCHEMA, FederalRegisterDocument: FEDERAL_REGISTER_DOCUMENT_SCHEMA, + AssistanceAward: ASSISTANCE_AWARD_SCHEMA, + AssistanceRecipient: ASSISTANCE_RECIPIENT_SCHEMA, + AssistanceRecipientAddress: ASSISTANCE_RECIPIENT_ADDRESS_SCHEMA, + AssistancePlaceOfPerformance: ASSISTANCE_PLACE_OF_PERFORMANCE_SCHEMA, + AssistancePeriodOfPerformance: ASSISTANCE_PERIOD_OF_PERFORMANCE_SCHEMA, + AssistanceAwardListing: ASSISTANCE_AWARD_LISTING_SCHEMA, + AssistanceTransaction: ASSISTANCE_TRANSACTION_SCHEMA, EbuyRequest: EBUY_REQUEST_SCHEMA, EbuyAttachment: EBUY_ATTACHMENT_SCHEMA, Agency: AGENCY_SCHEMA, diff --git a/tests/cassettes/assistance-direct-payments-detail.json b/tests/cassettes/assistance-direct-payments-detail.json new file mode 100644 index 0000000..eab6514 --- /dev/null +++ b/tests/cassettes/assistance-direct-payments-detail.json @@ -0,0 +1,172 @@ +{ + "version": 1, + "interactions": [ + { + "request": { + "method": "GET", + "url": "https://tango.makegov.com/api/assistance/direct_payments/?limit=1&shape=key" + }, + "response": { + "status": 200, + "headers": { + "content-type": "application/json", + "x-ratelimit-burst-limit": "1000", + "x-ratelimit-burst-remaining": "994", + "x-ratelimit-burst-reset": "40", + "x-ratelimit-daily-limit": "2000000", + "x-ratelimit-daily-remaining": "1999683", + "x-ratelimit-daily-reset": "41628", + "x-ratelimit-limit": "1000", + "x-ratelimit-remaining": "994", + "x-ratelimit-reset": "40", + "x-tango-api-version": "5.12.1" + }, + "body": { + "count": 56739924, + "next": "https://tango.makegov.com/api/assistance/direct_payments/?limit=1&shape=key&cursor=WyIyMDI2LTEwLTIyIiwgImQxMjI3ZmYzLWY4ZDEtNWRjMy05MDE0LTI1NzJmMzhjY2MyNiJd", + "previous": null, + "cursor": "WyIyMDI2LTEwLTIyIiwgImQxMjI3ZmYzLWY4ZDEtNWRjMy05MDE0LTI1NzJmMzhjY2MyNiJd", + "previous_cursor": null, + "results": [ + { + "key": "ASST_NON_12CCCEAUCEU5253218_012" + } + ] + } + } + }, + { + "request": { + "method": "GET", + "url": "https://tango.makegov.com/api/assistance/direct_payments/ASST_NON_12CCCEAUCEU5253218_012/?shape=key%2Ctransactions_count%2Clistings_count%2Clistings%28*%29" + }, + "response": { + "status": 200, + "headers": { + "content-type": "application/json", + "x-ratelimit-burst-limit": "1000", + "x-ratelimit-burst-remaining": "993", + "x-ratelimit-burst-reset": "39", + "x-ratelimit-daily-limit": "2000000", + "x-ratelimit-daily-remaining": "1999682", + "x-ratelimit-daily-reset": "41627", + "x-ratelimit-limit": "1000", + "x-ratelimit-remaining": "993", + "x-ratelimit-reset": "39", + "x-tango-api-version": "5.12.1" + }, + "body": { + "key": "ASST_NON_12CCCEAUCEU5253218_012", + "listings": [ + { + "is_primary": true, + "obligated": 45412.85, + "pragmatic_obligations": 45412.85, + "non_federal_funding_amount": null, + "indirect_cost_federal_share_amount": 0, + "face_value_of_loan": 0, + "loan_subsidy_cost": 0, + "transactions_count": 1, + "first_action_date": "2026-10-22", + "last_action_date": "2026-10-22", + "cfda_number": "10.051" + } + ], + "listings_count": 1, + "transactions_count": 1 + } + } + }, + { + "request": { + "method": "GET", + "url": "https://tango.makegov.com/api/assistance/direct_payments/ASST_NON_12CCCEAUCEU5253218_012/transactions/?limit=100&page=1" + }, + "response": { + "status": 200, + "headers": { + "content-type": "application/json", + "x-ratelimit-burst-limit": "1000", + "x-ratelimit-burst-remaining": "992", + "x-ratelimit-burst-reset": "39", + "x-ratelimit-daily-limit": "2000000", + "x-ratelimit-daily-remaining": "1999681", + "x-ratelimit-daily-reset": "41627", + "x-ratelimit-limit": "1000", + "x-ratelimit-remaining": "992", + "x-ratelimit-reset": "39", + "x-tango-api-version": "5.12.1" + }, + "body": { + "count": 1, + "next": null, + "previous": null, + "results": [ + { + "key": "12D2_12CCCEAUCEU5253218_-NONE-_10.051_-NONE-", + "modification_number": null, + "transaction_date": "2026-10-22", + "action_type": "A1", + "assistance_type_code": "F006", + "correction_delete_indicator": null, + "cfda_number": "10.051", + "obligated": 45412.85, + "pragmatic_obligations": 45412.85, + "non_federal_funding_amount": null, + "indirect_cost_federal_share_amount": 0, + "face_value_of_loan": 0, + "loan_subsidy_cost": 0, + "award_amount_basis": null, + "award_recipient_basis": null, + "action_date_fiscal_year": 2027, + "last_modified_date": "2026-10-23T14:19:37Z", + "initial_report_date": "2026-10-23T14:19:37Z" + } + ] + } + } + }, + { + "request": { + "method": "GET", + "url": "https://tango.makegov.com/api/assistance/direct_payments/ASST_NON_12CCCEAUCEU5253218_012/listings/?limit=25&page=1" + }, + "response": { + "status": 200, + "headers": { + "content-type": "application/json", + "x-ratelimit-burst-limit": "1000", + "x-ratelimit-burst-remaining": "991", + "x-ratelimit-burst-reset": "38", + "x-ratelimit-daily-limit": "2000000", + "x-ratelimit-daily-remaining": "1999680", + "x-ratelimit-daily-reset": "41626", + "x-ratelimit-limit": "1000", + "x-ratelimit-remaining": "991", + "x-ratelimit-reset": "38", + "x-tango-api-version": "5.12.1" + }, + "body": { + "count": 1, + "next": null, + "previous": null, + "results": [ + { + "cfda_number": "10.051", + "is_primary": true, + "obligated": 45412.85, + "pragmatic_obligations": 45412.85, + "non_federal_funding_amount": null, + "indirect_cost_federal_share_amount": 0, + "face_value_of_loan": 0, + "loan_subsidy_cost": 0, + "transactions_count": 1, + "first_action_date": "2026-10-22", + "last_action_date": "2026-10-22" + } + ] + } + } + } + ] +} diff --git a/tests/cassettes/assistance-grants-list.json b/tests/cassettes/assistance-grants-list.json new file mode 100644 index 0000000..9f81528 --- /dev/null +++ b/tests/cassettes/assistance-grants-list.json @@ -0,0 +1,191 @@ +{ + "version": 1, + "interactions": [ + { + "request": { + "method": "GET", + "url": "https://tango.makegov.com/api/assistance/grants/?limit=2&shape=key%2Cfain%2Curi%2Caward_date%2Cfiscal_year%2Cassistance_type%28code%2Cdescription%29%2Crecord_type%2Ccfda_number%2Cassistance_listing%28number%2Ctitle%29%2Ctitle%2Cobligated%2Cpragmatic_obligations%2Cface_value_of_loan%2Cloan_subsidy_cost%2Cawarding_office%28*%29%2Cfunding_office%28*%29%2Crecipient%28*%29%2Cplace_of_performance%28*%29%2Cperiod_of_performance%28start_date%2Ccurrent_end_date%29" + }, + "response": { + "status": 200, + "headers": { + "content-type": "application/json", + "x-ratelimit-burst-limit": "1000", + "x-ratelimit-burst-remaining": "998", + "x-ratelimit-burst-reset": "42", + "x-ratelimit-daily-limit": "2000000", + "x-ratelimit-daily-remaining": "1999687", + "x-ratelimit-daily-reset": "41630", + "x-ratelimit-limit": "1000", + "x-ratelimit-remaining": "998", + "x-ratelimit-reset": "42", + "x-tango-api-version": "5.12.1" + }, + "body": { + "count": 5759909, + "next": "https://tango.makegov.com/api/assistance/grants/?limit=2&shape=key%2Cfain%2Curi%2Caward_date%2Cfiscal_year%2Cassistance_type%28code%2Cdescription%29%2Crecord_type%2Ccfda_number%2Cassistance_listing%28number%2Ctitle%29%2Ctitle%2Cobligated%2Cpragmatic_obligations%2Cface_value_of_loan%2Cloan_subsidy_cost%2Cawarding_office%28%2A%29%2Cfunding_office%28%2A%29%2Crecipient%28%2A%29%2Cplace_of_performance%28%2A%29%2Cperiod_of_performance%28start_date%2Ccurrent_end_date%29&cursor=WyIyMDI2LTEwLTE2IiwgImIxZjg5ZjMwLWNiYjItNTdjOC04MDg2LTc0OGE4OGJmNzU4MSJd", + "previous": null, + "cursor": "WyIyMDI2LTEwLTE2IiwgImIxZjg5ZjMwLWNiYjItNTdjOC04MDg2LTc0OGE4OGJmNzU4MSJd", + "previous_cursor": null, + "results": [ + { + "assistance_listing": { + "number": "20.823", + "title": "Port Infrastructure Development Program" + }, + "assistance_type": { + "code": "F001", + "description": "Grant" + }, + "award_date": "2026-10-19", + "awarding_office": { + "organization_id": "261f885d-c8b6-5e0d-b2f8-e0799a4e1de2", + "office_code": "69A334", + "office_name": "MARITIME ADMINISTRATION", + "agency_code": "6938", + "agency_name": "MARITIME ADMINISTRATION", + "department_code": "069", + "department_name": "TRANSPORTATION, DEPARTMENT OF" + }, + "cfda_number": "20.823", + "face_value_of_loan": 0, + "fain": "693JF72640002", + "fiscal_year": 2027, + "funding_office": { + "organization_id": "261f885d-c8b6-5e0d-b2f8-e0799a4e1de2", + "office_code": "69A334", + "office_name": "MARITIME ADMINISTRATION", + "agency_code": "6938", + "agency_name": "MARITIME ADMINISTRATION", + "department_code": "069", + "department_name": "TRANSPORTATION, DEPARTMENT OF" + }, + "key": "ASST_NON_693JF72640002_069", + "loan_subsidy_cost": 0, + "obligated": 2000000, + "period_of_performance": { + "start_date": "2026-10-19", + "current_end_date": "2029-04-30" + }, + "place_of_performance": { + "city_name": "MOBILE", + "code": "AL36633", + "country": "USA", + "county_fips": "01097", + "county_name": "MOBILE", + "foreign_location": null, + "scope": "SINGLE ZIP CODE", + "state_code": "AL", + "state_fips": "01", + "state_name": "ALABAMA", + "zip_code": "36633", + "zip_plus_four": "1588" + }, + "pragmatic_obligations": 2000000, + "recipient": { + "uuid": "88f47661-874f-5298-8b0b-0971be0c746d", + "uei": "HS97F2C9NME3", + "duns": null, + "legal_business_name": "ALABAMA PORT AUTHORITY", + "legal_business_name_raw": "ALABAMA PORT AUTHORITY", + "county": "MOBILE", + "congressional_district": "AL-02", + "congressional_district_original": "AL-02", + "highest_owner": null, + "business_types": [ + { + "code": "A", + "description": "State Government" + } + ] + }, + "record_type": "2", + "title": "AWARD PURPOSE THE PROJECT INCLUDES ELECTRICAL…", + "uri": null + }, + { + "assistance_listing": { + "number": "20.933", + "title": "National Infrastructure Investments " + }, + "assistance_type": { + "code": "F001", + "description": "Grant" + }, + "award_date": "2026-10-16", + "awarding_office": { + "organization_id": "261f885d-c8b6-5e0d-b2f8-e0799a4e1de2", + "office_code": "69A334", + "office_name": "MARITIME ADMINISTRATION", + "agency_code": "6938", + "agency_name": "MARITIME ADMINISTRATION", + "department_code": "069", + "department_name": "TRANSPORTATION, DEPARTMENT OF" + }, + "cfda_number": "20.933", + "face_value_of_loan": 0, + "fain": "693JF72440048", + "fiscal_year": 2027, + "funding_office": { + "organization_id": "261f885d-c8b6-5e0d-b2f8-e0799a4e1de2", + "office_code": "69A334", + "office_name": "MARITIME ADMINISTRATION", + "agency_code": "6938", + "agency_name": "MARITIME ADMINISTRATION", + "department_code": "069", + "department_name": "TRANSPORTATION, DEPARTMENT OF" + }, + "key": "ASST_NON_693JF72440048_069", + "loan_subsidy_cost": 0, + "obligated": 2003532, + "period_of_performance": { + "start_date": "2026-10-16", + "current_end_date": "2028-08-31" + }, + "place_of_performance": { + "city_name": "CRISFIELD", + "code": "MD21817", + "country": "USA", + "county_fips": "24039", + "county_name": "SOMERSET", + "foreign_location": null, + "scope": "SINGLE ZIP CODE", + "state_code": "MD", + "state_fips": "24", + "state_name": "MARYLAND", + "zip_code": "21817", + "zip_plus_four": "1328" + }, + "pragmatic_obligations": 2003532, + "recipient": { + "uuid": "510bd6cc-9840-5adb-98d1-d8e5f74dff6b", + "uei": "EKPQAJKKV549", + "duns": null, + "legal_business_name": "CITY OF CRISFIELD", + "legal_business_name_raw": "CITY OF CRISFIELD", + "county": "SOMERSET", + "congressional_district": "MD-01", + "congressional_district_original": "MD-01", + "highest_owner": { + "uei": null, + "duns": null, + "legal_business_name": null, + "legal_business_name_raw": "CITY OF CRISFIELD" + }, + "business_types": [ + { + "code": "A", + "description": "State Government" + } + ] + }, + "record_type": "2", + "title": "AWARD PURPOSE THE PROJECT FUNDS THE FULL ENGINEERING…", + "uri": null + } + ] + } + } + } + ] +} diff --git a/tests/cassettes/assistance-loans-cursor.json b/tests/cassettes/assistance-loans-cursor.json new file mode 100644 index 0000000..e65ceb9 --- /dev/null +++ b/tests/cassettes/assistance-loans-cursor.json @@ -0,0 +1,83 @@ +{ + "version": 1, + "interactions": [ + { + "request": { + "method": "GET", + "url": "https://tango.makegov.com/api/assistance/loans/?limit=2&shape=key%2Caward_date" + }, + "response": { + "status": 200, + "headers": { + "content-type": "application/json", + "x-ratelimit-burst-limit": "1000", + "x-ratelimit-burst-remaining": "997", + "x-ratelimit-burst-reset": "41", + "x-ratelimit-daily-limit": "2000000", + "x-ratelimit-daily-remaining": "1999686", + "x-ratelimit-daily-reset": "41629", + "x-ratelimit-limit": "1000", + "x-ratelimit-remaining": "997", + "x-ratelimit-reset": "41", + "x-tango-api-version": "5.12.1" + }, + "body": { + "count": 29866842, + "next": "https://tango.makegov.com/api/assistance/loans/?limit=2&shape=key%2Caward_date&cursor=WyIyMDI2LTEwLTE1IiwgImZlNWM4ZDcyLWNkZjEtNTFhMi1hMDNkLThiZDlhMzI1ZjUxOCJd", + "previous": null, + "cursor": "WyIyMDI2LTEwLTE1IiwgImZlNWM4ZDcyLWNkZjEtNTFhMi1hMDNkLThiZDlhMzI1ZjUxOCJd", + "previous_cursor": null, + "results": [ + { + "award_date": "2026-10-15", + "key": "ASST_NON_8606059100_073" + }, + { + "award_date": "2026-10-15", + "key": "ASST_NON_8605109100_073" + } + ] + } + } + }, + { + "request": { + "method": "GET", + "url": "https://tango.makegov.com/api/assistance/loans/?cursor=WyIyMDI2LTEwLTE1IiwgImZlNWM4ZDcyLWNkZjEtNTFhMi1hMDNkLThiZDlhMzI1ZjUxOCJd&limit=2&shape=key%2Caward_date" + }, + "response": { + "status": 200, + "headers": { + "content-type": "application/json", + "x-ratelimit-burst-limit": "1000", + "x-ratelimit-burst-remaining": "996", + "x-ratelimit-burst-reset": "41", + "x-ratelimit-daily-limit": "2000000", + "x-ratelimit-daily-remaining": "1999685", + "x-ratelimit-daily-reset": "41629", + "x-ratelimit-limit": "1000", + "x-ratelimit-remaining": "996", + "x-ratelimit-reset": "41", + "x-tango-api-version": "5.12.1" + }, + "body": { + "count": 29866842, + "next": "https://tango.makegov.com/api/assistance/loans/?limit=2&cursor=WyIyMDI2LTEwLTE1IiwgImZkYmVkODk4LTIwZTQtNWZkNC05MDE5LWIwMjE2ZjBkOWQ4MiJd&shape=key%2Caward_date", + "previous": "https://tango.makegov.com/api/assistance/loans/?limit=2&cursor=eyJ2IjogWyIyMDI2LTEwLTE1IiwgImZkZGFiODVkLWVmYzctNWJhYi04ZDJmLWJjYWYzMjE3NmJhYSJdLCAiZCI6ICJwcmV2In0%3D&shape=key%2Caward_date", + "cursor": "WyIyMDI2LTEwLTE1IiwgImZkYmVkODk4LTIwZTQtNWZkNC05MDE5LWIwMjE2ZjBkOWQ4MiJd", + "previous_cursor": "eyJ2IjogWyIyMDI2LTEwLTE1IiwgImZkZGFiODVkLWVmYzctNWJhYi04ZDJmLWJjYWYzMjE3NmJhYSJdLCAiZCI6ICJwcmV2In0=", + "results": [ + { + "award_date": "2026-10-15", + "key": "ASST_NON_8604959106_073" + }, + { + "award_date": "2026-10-15", + "key": "ASST_NON_8603699109_073" + } + ] + } + } + } + ] +} diff --git a/tests/cassettes/assistance-other-filter.json b/tests/cassettes/assistance-other-filter.json new file mode 100644 index 0000000..0190e19 --- /dev/null +++ b/tests/cassettes/assistance-other-filter.json @@ -0,0 +1,48 @@ +{ + "version": 1, + "interactions": [ + { + "request": { + "method": "GET", + "url": "https://tango.makegov.com/api/assistance/other/?limit=2&pop_state=VA&shape=key%2Cplace_of_performance%28state_code%29" + }, + "response": { + "status": 200, + "headers": { + "content-type": "application/json", + "x-ratelimit-burst-limit": "1000", + "x-ratelimit-burst-remaining": "995", + "x-ratelimit-burst-reset": "40", + "x-ratelimit-daily-limit": "2000000", + "x-ratelimit-daily-remaining": "1999684", + "x-ratelimit-daily-reset": "41628", + "x-ratelimit-limit": "1000", + "x-ratelimit-remaining": "995", + "x-ratelimit-reset": "40", + "x-tango-api-version": "5.12.1" + }, + "body": { + "count": 140023, + "next": "https://tango.makegov.com/api/assistance/other/?limit=2&shape=key%2Cplace_of_performance%28state_code%29&pop_state=VA&cursor=WyIyMDI2LTEwLTEyIiwgImQ5Yzk2MjllLTMzMGYtNWQwYy04NDliLWFiYWRjYjRmMjVmYyJd", + "previous": null, + "cursor": "WyIyMDI2LTEwLTEyIiwgImQ5Yzk2MjllLTMzMGYtNWQwYy04NDliLWFiYWRjYjRmMjVmYyJd", + "previous_cursor": null, + "results": [ + { + "key": "ASST_NON_C10111870597904620260822000000_070", + "place_of_performance": { + "state_code": "VA" + } + }, + { + "key": "ASST_NON_C99999SF0072903220260804000000_070", + "place_of_performance": { + "state_code": "VA" + } + } + ] + } + } + } + ] +} diff --git a/tests/integration/assistance.test.ts b/tests/integration/assistance.test.ts new file mode 100644 index 0000000..be9962c --- /dev/null +++ b/tests/integration/assistance.test.ts @@ -0,0 +1,58 @@ +import { describeIntegration, integrationClient } from "./harness.js"; +import { expectFields, expectNonEmpty } from "./validation.js"; + +describeIntegration("assistance awards (recorded)", () => { + it("lists grants with the API's default shape", async () => { + const client = integrationClient("assistance-grants-list"); + const res = await client.listAssistanceGrants({ limit: 2 }); + const first = expectNonEmpty(res); + expectFields(first, [ + "key", + "fain", + "award_date", + "assistance_type", + "cfda_number", + "obligated", + "pragmatic_obligations", + "awarding_office", + "recipient", + ]); + expect(first.award_date).toBeInstanceOf(Date); + expect(typeof first.obligated).toBe("string"); + }); + + it("paginates loans with a cursor", async () => { + const client = integrationClient("assistance-loans-cursor"); + const page1 = await client.listAssistanceLoans({ limit: 2, shape: "key,award_date" }); + expectNonEmpty(page1); + expect(page1.cursor).toBeTruthy(); + + const page2 = await client.listAssistanceLoans({ limit: 2, shape: "key,award_date", cursor: page1.cursor }); + const first2 = expectNonEmpty(page2); + expect(first2.key).not.toBe(page1.results[0].key); + }); + + it("filters other assistance by place of performance", async () => { + const client = integrationClient("assistance-other-filter"); + const res = await client.listAssistanceOther({ limit: 2, pop_state: "VA", shape: "key,place_of_performance(state_code)" }); + const first = expectNonEmpty(res); + expect((first.place_of_performance as Record).state_code).toBe("VA"); + }); + + it("gets a direct payment with its transactions and listings", async () => { + const client = integrationClient("assistance-direct-payments-detail"); + const list = await client.listAssistanceDirectPayments({ limit: 1, shape: "key" }); + const key = String(expectNonEmpty(list).key); + + const award = await client.getAssistanceDirectPayment(key, { shape: "key,transactions_count,listings_count,listings(*)" }); + expect(award.key).toBe(key); + + const transactions = await client.listAssistanceDirectPaymentTransactions(key); + expect(transactions.count).toBe(award.transactions_count); + expectFields(transactions.results[0] as unknown as Record, ["key", "transaction_date", "action_type", "obligated"]); + + const listings = await client.listAssistanceDirectPaymentListings(key); + expect(listings.count).toBe(award.listings_count); + expect(listings.results[0].is_primary).toBe(true); + }); +}); diff --git a/tests/unit/client.assistance.test.ts b/tests/unit/client.assistance.test.ts new file mode 100644 index 0000000..dea5a48 --- /dev/null +++ b/tests/unit/client.assistance.test.ts @@ -0,0 +1,340 @@ +/** + * Tests for the financial-assistance award endpoints (`/api/assistance/{grants,loans,direct_payments,other}/`). + * + * Covers the request contract via the injected fetchImpl mock — paths, filters under the API's own param names, cursor pagination on the lists, page pagination on the sub-routes — plus the shape schema the SDK registers for `AssistanceAward`. + */ + +import { readFileSync } from "node:fs"; +import { TangoClient } from "../../src/client.js"; +import type { ListAssistanceAwardsOptions } from "../../src/client.js"; +import { ShapeConfig } from "../../src/config.js"; +import type { AssistanceAward, AssistanceTransaction } from "../../src/models/index.js"; +import { SchemaRegistry } from "../../src/shapes/schema.js"; + +type RecordedCall = { url: string }; + +const EMPTY_PAGE = { count: 0, next: null, previous: null, results: [] }; + +function makeClient(...bodies: unknown[]): { client: TangoClient; calls: RecordedCall[] } { + const calls: RecordedCall[] = []; + const fetchImpl = (async (url: string | URL) => { + calls.push({ url: String(url) }); + const body = bodies.length > 1 ? bodies[calls.length - 1] : (bodies[0] ?? EMPTY_PAGE); + 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 url(calls: RecordedCall[], i = 0): URL { + return new URL(calls[i].url); +} + +const KEY = "ASST_NON_693JF72440048_069"; + +const AWARD: AssistanceAward = { + key: KEY, + fain: "693JF72440048", + uri: null, + award_date: "2026-10-16", + fiscal_year: 2027, + assistance_type: { code: "F001", description: "Grant" }, + record_type: "2", + cfda_number: "20.933", + assistance_listing: { number: "20.933", title: "National Infrastructure Investments" }, + title: "AWARD PURPOSE THE PROJECT FUNDS THE FULL ENGINEERING", + obligated: 2003532.0, + pragmatic_obligations: 2003532.0, + face_value_of_loan: 0.0, + loan_subsidy_cost: 0.0, + awarding_office: { organization_id: "261f885d-c8b6-5e0d-b2f8-e0799a4e1de2", office_code: "69A334", office_name: "MARITIME ADMINISTRATION" }, + funding_office: { organization_id: "261f885d-c8b6-5e0d-b2f8-e0799a4e1de2", office_code: "69A334", office_name: "MARITIME ADMINISTRATION" }, + recipient: { + uuid: "510bd6cc-9840-5adb-98d1-d8e5f74dff6b", + uei: "EKPQAJKKV549", + duns: null, + legal_business_name: "CITY OF CRISFIELD", + highest_owner: { uei: null, duns: null, legal_business_name: null, legal_business_name_raw: "CITY OF CRISFIELD" }, + business_types: [{ code: "A", description: "State Government" }], + }, + place_of_performance: { country: "USA", state_code: "MD", city_name: "CRISFIELD", zip_code: "21817" }, + period_of_performance: { start_date: "2026-10-16", current_end_date: "2028-08-31" }, +}; + +const TRANSACTION: AssistanceTransaction = { + key: "6938_693JF72440048_-NONE-_20.933_-NONE-", + modification_number: null, + transaction_date: "2026-10-16", + action_type: "A1", + obligated: 2003532.0, + action_date_fiscal_year: "2027", + last_modified_date: "2026-10-19T17:21:07Z", +}; + +const GROUPS = [ + { + group: "grants", + list: "listAssistanceGrants", + get: "getAssistanceGrant", + tx: "listAssistanceGrantTransactions", + listings: "listAssistanceGrantListings", + }, + { + group: "loans", + list: "listAssistanceLoans", + get: "getAssistanceLoan", + tx: "listAssistanceLoanTransactions", + listings: "listAssistanceLoanListings", + }, + { + group: "direct_payments", + list: "listAssistanceDirectPayments", + get: "getAssistanceDirectPayment", + tx: "listAssistanceDirectPaymentTransactions", + listings: "listAssistanceDirectPaymentListings", + }, + { + group: "other", + list: "listAssistanceOther", + get: "getAssistanceOther", + tx: "listAssistanceOtherTransactions", + listings: "listAssistanceOtherListings", + }, +] as const; + +type AnyMethod = (...args: unknown[]) => Promise>; + +function method(client: TangoClient, name: string): AnyMethod { + return (client as unknown as Record)[name].bind(client); +} + +const ALL_FILTERS: ListAssistanceAwardsOptions = { + key: "K1|K2", + fain: "693JF72440048", + uri: "URI-1", + funding_opportunity_number: "MAPID24001", + uei: "EKPQAJKKV549|HS97F2C9NME3", + recipient: "crisfield", + awarding_agency: "DOT", + funding_agency: "069", + cfda_number: "20.933|93.778", + assistance_type: "F001", + record_type: "2|3", + business_funds_indicator: "NON", + disaster_emergency_fund_code: "L", + award_date: "2026-10-16", + award_date_gte: "2026-01-01", + award_date_lte: "2026-12-31", + fiscal_year: 2027, + fiscal_year_gte: 2020, + fiscal_year_lte: "2027", + pop_start_date_gte: "2026-01-01", + pop_start_date_lte: "2026-12-31", + pop_end_date_gte: "2027-01-01", + pop_end_date_lte: "2029-12-31", + pop_state: "MD|VA", + pop_country: "USA", + pop_zip: "21817", + obligated_gte: 100000, + obligated_lte: "5000000.50", + search: "dock replacement", + ordering: "-pragmatic_obligations", +}; + +describe("TangoClient: assistance award lists", () => { + it("listAssistanceGrants sends every filter under the API's param name", async () => { + const { client, calls } = makeClient(); + await client.listAssistanceGrants({ ...ALL_FILTERS, limit: 10 }); + + const p = url(calls).searchParams; + expect(Object.fromEntries([...p.entries()].filter(([k]) => !["limit", "shape"].includes(k)))).toEqual({ + ...Object.fromEntries(Object.entries(ALL_FILTERS).map(([k, v]) => [k, String(v)])), + }); + expect(p.get("limit")).toBe("10"); + }); + + it.each(GROUPS)("$list hits /api/assistance/$group/ with the API's default shape", async ({ group, list }) => { + const { client, calls } = makeClient(); + await method(client, list)(); + + expect(url(calls).pathname).toBe(`/api/assistance/${group}/`); + expect(url(calls).searchParams.get("shape")).toBe(ShapeConfig.ASSISTANCE_AWARDS_MINIMAL); + expect(url(calls).searchParams.get("limit")).toBe("25"); + }); + + it("forwards the cursor and never sends page, since the lists are keyset-only", async () => { + const { client, calls } = makeClient(); + await client.listAssistanceLoans({ cursor: "WyIyMDI2LTEwLTE2Il0", page: 3, limit: 500 }); + + const p = url(calls).searchParams; + expect(p.get("cursor")).toBe("WyIyMDI2LTEwLTE2Il0"); + expect(p.has("page")).toBe(false); + expect(p.get("limit")).toBe("100"); + }); + + it("drops null and undefined filters instead of sending them", async () => { + const { client, calls } = makeClient(); + await client.listAssistanceOther({ pop_state: undefined, uei: null as unknown as string, cfda_number: "10.703" }); + + const p = url(calls).searchParams; + expect(p.has("pop_state")).toBe(false); + expect(p.has("uei")).toBe(false); + expect(p.get("cfda_number")).toBe("10.703"); + }); + + it("materializes the default shape: dates parsed, money as decimal strings, nested objects kept", async () => { + const next = "http://localhost:8000/api/assistance/grants/?limit=25&cursor=WyIyMDI2LTEwLTE2Il0"; + const { client } = makeClient({ count: 5759909, next, previous: null, results: [AWARD] }); + const page = await client.listAssistanceGrants(); + const row = page.results[0]; + + expect(page.cursor).toBe("WyIyMDI2LTEwLTE2Il0"); + expect(row.key).toBe(KEY); + expect(row.award_date).toEqual(new Date("2026-10-16")); + expect(row.obligated).toBe("2003532"); + expect(row.face_value_of_loan).toBe("0"); + expect(row.assistance_type).toEqual({ code: "F001", description: "Grant" }); + expect(row.period_of_performance).toEqual({ start_date: new Date("2026-10-16"), current_end_date: new Date("2028-08-31") }); + expect(row.recipient).toMatchObject({ uei: "EKPQAJKKV549", business_types: [{ code: "A", description: "State Government" }] }); + expect((row.recipient as Record).highest_owner).toEqual(AWARD.recipient?.highest_owner); + }); + + it("sends flat and joiner together and unflattens the response", async () => { + const { client, calls } = makeClient({ + count: 1, + next: null, + previous: null, + results: [{ key: KEY, recipient__uei: "EKPQAJKKV549" }], + }); + const page = await client.listAssistanceDirectPayments({ shape: "key,recipient(uei)", flat: true, joiner: "__" }); + + const p = url(calls).searchParams; + expect(p.get("flat")).toBe("true"); + expect(p.get("joiner")).toBe("__"); + expect(page.results[0]).toEqual({ key: KEY, recipient: { uei: "EKPQAJKKV549" } }); + }); + + it("rejects a contract field the AssistanceAward schema doesn't have", async () => { + const { client } = makeClient({ count: 1, next: null, previous: null, results: [{ key: KEY }] }); + await expect(client.listAssistanceGrants({ shape: "key,piid" })).rejects.toThrow("piid"); + }); + + it("iterateAssistanceLoans follows the cursor across pages", async () => { + const base = "http://localhost:8000/api/assistance/loans/"; + const { client, calls } = makeClient( + { count: 3, next: `${base}?limit=2&cursor=C2`, previous: null, results: [{ key: "L1" }, { key: "L2" }] }, + { count: 3, next: null, previous: `${base}?limit=2&cursor=C1`, results: [{ key: "L3" }] }, + ); + + const keys: unknown[] = []; + for await (const award of client.iterateAssistanceLoans({ limit: 2, pop_state: "TX", shape: "key" })) keys.push(award.key); + + expect(keys).toEqual(["L1", "L2", "L3"]); + expect(url(calls, 1).searchParams.get("cursor")).toBe("C2"); + expect(url(calls, 1).searchParams.get("pop_state")).toBe("TX"); + }); +}); + +describe("TangoClient: assistance award detail and sub-routes", () => { + it.each(GROUPS)("$get uses /api/assistance/$group/{key}/ with the default shape", async ({ group, get }) => { + const { client, calls } = makeClient(AWARD); + const award = await method(client, get)(KEY); + + expect(url(calls).pathname).toBe(`/api/assistance/${group}/${KEY}/`); + expect(url(calls).searchParams.get("shape")).toBe(ShapeConfig.ASSISTANCE_AWARDS_MINIMAL); + expect(award.key).toBe(KEY); + }); + + it("parses the transactions expand, keeping action_date_fiscal_year as served", async () => { + const { client } = makeClient({ key: KEY, transactions: [TRANSACTION] }); + const award = await client.getAssistanceGrant(KEY, { shape: "key,transactions(*)" }); + const tx = (award.transactions as Array>)[0]; + + expect(tx.transaction_date).toEqual(new Date("2026-10-16")); + expect(tx.last_modified_date).toEqual(new Date("2026-10-19T17:21:07Z")); + expect(tx.obligated).toBe("2003532"); + expect(tx.action_date_fiscal_year).toBe("2027"); + }); + + it("URL-encodes the key", async () => { + const { client, calls } = makeClient(AWARD); + await client.getAssistanceOther("A/B C"); + expect(url(calls).pathname).toBe("/api/assistance/other/A%2FB%20C/"); + }); + + it.each(GROUPS)("$tx pages /api/assistance/$group/{key}/transactions/ at 100 by default, capped at 500", async ({ group, tx }) => { + const body = { count: 1, next: null, previous: null, results: [{ ...TRANSACTION, action_date_fiscal_year: 2027 }] }; + const { client, calls } = makeClient(body, body); + const page = await method(client, tx)(KEY); + await method(client, tx)(KEY, { page: 2, limit: 1000 }); + + expect(url(calls).pathname).toBe(`/api/assistance/${group}/${KEY}/transactions/`); + expect(Object.fromEntries(url(calls).searchParams)).toEqual({ page: "1", limit: "100" }); + expect(Object.fromEntries(url(calls, 1).searchParams)).toEqual({ page: "2", limit: "500" }); + expect((page.results as AssistanceTransaction[])[0]).toEqual({ ...TRANSACTION, action_date_fiscal_year: 2027 }); + }); + + it.each(GROUPS)("$listings pages /api/assistance/$group/{key}/listings/, capped at 100", async ({ group, listings }) => { + const row = { cfda_number: "20.933", is_primary: true, obligated: 2003532.0, transactions_count: 1, first_action_date: "2026-10-16" }; + const body = { count: 1, next: null, previous: null, results: [row] }; + const { client, calls } = makeClient(body, body); + const page = await method(client, listings)(KEY); + await method(client, listings)(KEY, { limit: 250 }); + + expect(url(calls).pathname).toBe(`/api/assistance/${group}/${KEY}/listings/`); + expect(Object.fromEntries(url(calls).searchParams)).toEqual({ page: "1", limit: "25" }); + expect(url(calls, 1).searchParams.get("limit")).toBe("100"); + expect(page.results).toEqual([row]); + }); + + it.each(GROUPS.flatMap(({ get, tx, listings }) => [get, tx, listings]))("%s rejects an empty key before issuing a request", async (name) => { + const { client, calls } = makeClient(); + await expect(method(client, name)("")).rejects.toThrow("Assistance award key is required"); + expect(calls).toHaveLength(0); + }); +}); + +describe("AssistanceAward shape schema", () => { + const registry = new SchemaRegistry(); + const contract = JSON.parse(readFileSync(new URL("../../contracts/filter_shape_contract.json", import.meta.url), "utf8")); + const shape = contract.resources["assistance/grants"].runtime.shape as { fields: string[]; expands: Record }; + + it("the vendored contract publishes all four groups with one shape", () => { + expect(shape.fields.length).toBeGreaterThanOrEqual(45); + expect(Object.keys(shape.expands)).toContain("recipient_address"); + for (const group of ["loans", "direct_payments", "other"]) { + expect(contract.resources[`assistance/${group}`].runtime.shape).toEqual(shape); + } + }); + + it("knows every field and expand the contract serves", () => { + const known = registry.getSchema("AssistanceAward").fields; + expect([...shape.fields, ...Object.keys(shape.expands)].filter((field) => known[field] === undefined)).toEqual([]); + for (const [expand, node] of Object.entries(shape.expands)) { + const nested = known[expand].nestedModel as string; + const nestedFields = registry.getSchema(nested).fields; + expect( + node.fields.filter((field) => nestedFields[field] === undefined), + `${expand} -> ${nested}`, + ).toEqual([]); + } + }); + + it("the default shape is exactly the API's own default", () => { + expect(ShapeConfig.ASSISTANCE_AWARDS_MINIMAL).toBe( + "key,fain,uri,award_date,fiscal_year,assistance_type(code,description),record_type,cfda_number,assistance_listing(number,title),title," + + "obligated,pragmatic_obligations,face_value_of_loan,loan_subsidy_cost,awarding_office(*),funding_office(*),recipient(*)," + + "place_of_performance(*),period_of_performance(start_date,current_end_date)", + ); + }); + + it.each(["listings", "transactions", "obligations_by_fiscal_year"])("%s is a list", (field) => { + expect(registry.getField("AssistanceAward", field).isList).toBe(true); + }); +});